实现滑动效果的方式
在Android中有三种方式可以实现View的滑动效果: 1、通过View本身提供的 scrollTo(intx,inty)或者scrollBy(intx,inty)方法来实现View中内容滑动的效果。(内容滑动) 2、通过动画(最好使用属性动画)给View施加平移效果来实现滑动。(View滑动) 3、通过改变View的LayoutParams使得View重新布局来实现滑动效果。
三者对比: 1、scrollTo/scrollBy:操作简单适合对View内容的滑动。 2、动画:操作简单,主要适合没有交互的View和实现复杂的动画效果。 3、改变布局参数:操作稍微复杂,适用于有交互的View。

scroll滑动原理
在此处主要分析scroll方式的滑动。
  • 首先,弄清楚两个变量的概念:mScrollX和mScrollY
源码
<span style="color:#009900;">/**     * The offset, in pixels, by which the content of this view is scrolled     * horizontally.     * {@hide}     */    @ViewDebug.ExportedProperty(category = "scrolling")</span>    protected int mScrollX;    <span style="color:#009900;">/**     * The offset, in pixels, by which the content of this view is scrolled     * vertically.     * {@hide}     */    @ViewDebug.ExportedProperty(category = "scrolling")</span>    protected int mScrollY;


mScrollXmScrollY的变化规律:

(1).mScrollX的值总是等于View左边缘View内容左边缘水平方向距离(mScrollX=X1-X2,,其中X1,表示View的左边缘,其中X2,表示View内容的左边缘),当View内容的左边缘位于View的左边缘的左边时,mScrollX大于零,即mScrollX为正值,反之为负值;

(2).mScrollY的值总是等于View上边缘View内容上边缘竖直方向距离(mScrollY=Y1-Y2,,其中Y1,表示View的上边缘,其中Y2,表示View内容的上边缘),当View内容的上边缘位于View的上边缘的上边时,mScrollY大于零,即mScrollY为正值,反之为负值;

默认情况下,mScrollX和mScrollY都等于0(我的理解是在刚进入程序,scroll之前mScrollX=0和mScrollY=0)。


  • scrollTo(int x,int y)的源码:
<span style="color:#009900;">/**     * Set the scrolled position of your view. This will cause a call to     * {@link #onScrollChanged(int, int, int, int)} and the view will be     * invalidated.     * @param x the x position to scroll to     * @param y the y position to scroll to     */</span>    public void scrollTo(int x, int y) {        if (mScrollX != x || mScrollY != y) {            int oldX = mScrollX;            int oldY = mScrollY;            mScrollX = x;            mScrollY = y;            invalidateParentCaches();            onScrollChanged(mScrollX, mScrollY, oldX, oldY);            if (!awakenScrollBars()) {                postInvalidateOnAnimation();            }        }    }

在源码中,该方法是将View的内容移动到指定位置,可以看出每次 scrollTo(int x,int y)时,都会进行if (mScrollX!= x || mScrollY!= y)判断,检查目的点的坐标是否和偏移量一样,因为scrollTo()是移动到指定的点,如果这次移动的点的坐标和上次偏移量一样,也就是说这次移动和上次移动的坐标是同一个,那么就没有必要进行移动了。 效果图:



上面几幅图就是,通过scrollTo进行上下左右移动后的效果,如果仔细看就会发现为什么当参数的正负与坐标系的正负相反呢?等会儿介绍方向问题。
  • scrollBy(int x,int y)的源码
/**     * Move the scrolled position of your view. This will cause a call to     * {@link #onScrollChanged(int, int, int, int)} and the view will be     * invalidated.     * @param x the amount of pixels to scroll by horizontally     * @param y the amount of pixels to scroll by vertically     */    public void scrollBy(int x, int y) {        scrollTo(mScrollX + x, mScrollY + y);    }


从源码中可以看出, scrollBy(int x,int y)的本质与 scrollTo(int x,int y)是一致的, scrollTo(int x,int y)是从当前偏移量移动到指定点,而 scrollBy(int x,int y)是在当前偏移量的基础上根据参数提供的偏移量移动,所以 在参数不变的情况下,scrollTo(int x,int y)只能移动一次,而 scrollBy(int x,int y)可以一直地移动下去。
  • scrollTo(int x,int y)移动的方向
在源码中,当滑动时会经过 invalidate(left,top,right,bottom)方法,而在这个方法中有 tmpr.set(l-scrollX,t-scrollY,r-scrollX,b-scrollY)这步操作,所以当scrollTo(int x,int y)的参数为正数时,绘制内容的区域会向坐标反方向移动。 所以scrollTo的移动方向:

弹性滑动 由于scrollTo(int x,int y)滑动时,会瞬间移动,实际的体验效果不好,所以我们要实现渐进式滑动。渐进式滑动的思想是:将一次大的滑动分成若干次小的滑动,并在一个时间段内完成。 能够实现弹性滑动的方式很多,如通过Scroller、动画、(Handler+postDelay)或(Thread+sleep)等。 在此处使用Scroller实现。 首先,介绍Scroller的几个常用方法:
  • startScroll(intstartX,intstartY,intdx,intdy,intduration)
从方法名字来看应该是滑动开始的地方,事实上我们在使用的时候也是先调用这个方法的,该方法的主要作用是:一个构造方法用来初始化赋值的,比如设置滚动模式、开始时间,持续时间、起始坐标、结束坐标等等,并没有任何对View的滚动操作。 源码:
 /**     * Start scrolling by providing a starting point, the distance to travel,     * and the duration of the scroll.     *      * @param startX Starting horizontal scroll offset in pixels. Positive     *        numbers will scroll the content to the left.     * @param startY Starting vertical scroll offset in pixels. Positive numbers     *        will scroll the content up.     * @param dx Horizontal distance to travel. Positive numbers will scroll the     *        content to the left.     * @param dy Vertical distance to travel. Positive numbers will scroll the     *        content up.     * @param duration Duration of the scroll in milliseconds.     */    public void startScroll(int startX, int startY, int dx, int dy, int duration) {        mMode = SCROLL_MODE;        mFinished = false;        mDuration = duration;        mStartTime = AnimationUtils.currentAnimationTimeMillis();        mStartX = startX;        mStartY = startY;        mFinalX = startX + dx;        mFinalY = startY + dy;        mDeltaX = dx;        mDeltaY = dy;        mDurationReciprocal = 1.0f / (float) mDuration;    }



源码中验证了,上述结论。使用Scroller进行滑动时,在startScroll(intstartX,intstartY,intdx,intdy,intduration),方法中只是进行了初始化。
  • computeScrollOffset()
方法主要是根据当前已经消逝的时间来计算当前的坐标点,并且保存在mCurrX和mCurrY值中。之前在startScroll()方法的时候获取了当前的动画毫秒并赋值给了mStartTime,在computeScrollOffset()中再一次调用AnimationUtils.currentAnimationTimeMillis()来获取动画毫秒减去mStartTime就是消逝时间了。然后进去if判断,如果动画持续时间小于设置的滚动持续时间mDuration,则是SCROLL_MODE,再根据Interpolator来计算出在该时间段里面移动的距离,移动的距离是根据这个消逝时间乘以mDurationReciprocal,就得到一个相对偏移量,再进行Math.round(x * mDeltaX)计算,就得到最后的偏移量,然后赋值给mCurrX, mCurrY,所以mCurrX、 mCurrY的值也是一直变化的。总结一下该方法的作用就是,计算在0到mDuration时间段内滚动的偏移量,并且判断滚动是否结束,true代表还没结束,false则表示滚动结束了。
Scroller实现弹性滑动的流程: 首先是View通过Scroller的startScroll(intstartX,intstartY,intdx,intdy,intduration)方法进行初始化。 然后,会调用View的 invalidate()或postInvalidate()进行重绘。 接着,绘制View的时候会触发computeScroll()方法,接着重写computeScroll(),在computeScroll()里面先调用Scroller的computeScrollOffset()方法来判断滚动是否结束,如果滚动没有结束就调用scrollTo()方法来进行滚动。 最后,scrollTo()方法虽然会重新绘制View,但是还是要手动调用下invalidate()或者postInvalidate()来触发界面重绘,重新绘制View又触发computeScroll(),所以就进入一个递归循环阶段,这样就实现在某个时间段里面滚动某段距离的一个平滑的滚动效果。




















更多相关文章

  1. Service和Activity通讯的3种常用方式示例
  2. 【Android】高效ListView
  3. Android(安卓)架构组件(一)——Lifecycle
  4. Android(安卓)调用系统相机拍照保存以及调用系统相册的方法
  5. 学习Android(安卓)--从现在开始
  6. Android(安卓)studio gradle build 太慢,有时会卡住的解决方法
  7. Android(安卓)TV Input Framework(TIF)--显示Tv Input
  8. 使用Lint 和 Annotations来提升代码质量
  9. Android(安卓)databinding(详解三)--自定义属性使用

随机推荐

  1. 第25章 0119-组件的知识,学习心得,笔记(Vue,
  2. PHP基础知识:数组相关函数和操作
  3. 初探jQuery($(),attr(),css(),val(),html(
  4. 数组排序、数组合并
  5. 通讯录的实现
  6. 什么是爬虫?Python爬虫的工作流程怎样?
  7. 【我的Linux,我做主!】Docker容器技术基础
  8. grid对齐属性、grid实战php网站首页、媒
  9. 第26章 0120-vue路由原理与实现,学习心得,
  10. SQL2005完整+差异+日志备份还原策略