android_Scroller 弹性滑动源码分析

来源:互联网 发布:sql数据库文件修复 编辑:程序博客网 时间:2024/06/14 11:00

1、scrollTo/scrollBy

总所周知,使用 View 的 scrollTo 或者 scrollBy 方法实现 View 内容的滑动是一瞬间完成的,毫无过度可言,那么 Android 提供 Scroller 就可以解决这个问题,让 View 内容的滑动可以在一段时间内完成。

mImageView.scrollTo(100,100);

上面的示例代码表示 mImageView 的内容向左上角移动 100px,上面说过了,这种方式是一瞬间完成的,很坚硬。

为了解决这个问题,我们引入的 Scroller 类,下面看看如何使用这个类实现弹性滑动

2、Scroller 的实现原理

  • 准备工作:使用 Scroller#startScroll(…) 方法确定 View 内容的位置和需要滑动的距离,注意这个方法只是对 Scroller 对象的中一些属性进行赋值(具体看下下面源码部分),并没有产生任何的滑动操作。

  • 准备工作完成之后,该如何利用 Scroller 实现弹性滑动

    通过调用 View#invalidate() 方法去触发 View 的重绘操作,因为 View#draw 方法中会回调 computeScroll() 这个方法,而它是个空方法,我们只要在 View 中复写这个方法,就可以在每次 View 重绘制时获取当前 View 内容需要滑动的距离,然后调用 scrollTo 进行滑动。
  • 现在知道是通过 View#invalidate() 方法可以触发 View 进行重绘,那么如何在重绘的回调中取计算当前 View 内容需要滑动的距离呢?

    • 因为 View#computeScroll 方法会在 View#invalidate() 中回调,那么我们多次调用 invalidate 方法就可以多次回调了,并且在每一次回调中取设置当前 View 需要滑动到指定的位置。

    • 如何计算 View 需要滑动的距离呢? 在 Scroller#onComputeScrollOffset() 方法内部会根据时间去计算当前 View 内容的 scrollX 和 scrollY 的值,因此在 View#computeScroll() 方法中去对调用 onComputeScrollOffset 进行计算即可,然后获取计算后的值,然后调用 scrollTo 进行滑动即可。

3、示例代码

我们在第 2 点中说了很多,可能会有点迷糊,下面以实际代码来演示可能会更加清晰一些。下面是第 2 点实现原理的具体代码实现:

//定义Scroller对象Scroller mScroller = new Scroller(mContext);/*** @des 滑动到某个位置* @param dx 表示x方向需要滑动的大小* @param dy 表示y方向需要滑动的大小*/private void smoothScrollBy(int dx, int dy) {    int scrollX = getScrollX();    int scrollY = getScrollY();    //这个方法是在初始化坐标(scrollX,scrollY)水平方向滑动dx像素,竖直方向滑动dy个像素,滑动时间为500ms。    mScroller.startScroll(scrollX, scrollY, dx, dy, 500);    //该方法标记该 View 会进行重绘,并且回调 computeScroll() 方法。    invalidate();}//重写View的computeScroll()方法,该方法会在View绘制的时候调用@Overridepublic void computeScroll() {    //计算当前的 scrollX 和 scrollY 的值,并且根据返回值判断滑动是否结束。    if (mScroller.computeScrollOffset()) {        //滚动结束        scrollTo(mScroller.getCurrX(), mScroller.getCurrY());//计算后的 scrollX 和 scrollY 的值        postInvalidate();//每次滑动之后,都要重绘一遍    }}

4、源码分析

4.1、弹性滑动入口 startScroll(int startX, int startY, int dx, int dy)

/** * 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;}

4.2、scrollTo 是在 computeScroll() 回调中调用的

在调用 Scroller#startScroll 方法之后,需要再调用invalidate方法,这个方法会让View进行重新绘制,也就
是说会重新的调用 draw() 方法,查阅View#draw()方法可以看到,该方法中还调用 computeScroll 方法,并且该
方法是一个空方法,我们重写该方法,在这个方法中我们捕获每次 View 重绘的时机,然后再进行 scrollTo 滑动即可。

  • View 中的 draw 方法调用 computeScroll 的代码片段
...if (!drawingWithRenderNode) {    computeScroll();//绘制时,调用该方法    sx = mScrollX;    sy = mScrollY;}...

4.3、View中的computeScroll方法,它是一个空方法,源码如下,它的源码大概的意思如下:该方法用于在弹性滑动时更新 View 的 scrollX 和 scrollY 的值。

/** * Called by a parent to request that a child update its values for mScrollX * and mScrollY if necessary. This will typically be done if the child is * animating a scroll using a {@link android.widget.Scroller Scroller} * object. */public void computeScroll() {}

然而在每次调用 srcollTo 进行滑动之后,我们总得知道什么时候滑动到终点啊和下次要滑动的位置?这个时候就得了解另一个方法Scroller#computeScrollOffset()

4.4、computeScrollOffset 分析

从方法解释中可以大概的知道:
1.方法返回 true 表示滑动还没结束,返回 false 代表滑动已经结束,如果没有结束,获取计算后的 scrollX 和 scrllY 的值进行再次的滑动。
2.该方法可以计算滑动后的位置 scrollX 和 scrollY 的值,也就是 mCurrX 和 mCurrY 的值,供下次 View#scroll(scrollX,scrollY) 使用。

/*** Call this when you want to know the new location.  If it returns true,* the animation is not yet finished.* */ public boolean computeScrollOffset() {   if (mFinished) {//使用 mFinished 标记滑动是否结束       return false;   }   int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);   if (timePassed < mDuration) {       switch (mMode) {       case SCROLL_MODE:           //timePassed * mDurationReciprocal           //timePassed:当前时间 - 开始时间           //mDurationRecipracal:1f/mDuration           //根据流逝的时间计算出一个百分比值           final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);           //更新 mCurrX 和 mCurrY 的值           mCurrX = mStartX + Math.round(x * mDeltaX);//四舍五入操作           mCurrY = mStartY + Math.round(x * mDeltaY);           break;       case FLING_MODE://这种方式暂时不分析。            ...            break;        }    }    else {//滑动时间到        mCurrX = mFinalX;        mCurrY = mFinalY;        mFinished = true;    }    return true;}

5、总结

Scroller 是用于解决 View#scrollTo/scrollBy() 滑动效果问题,借助它可以实现弹性滑动效果。实现原理简单来说就是 View 内容在可以在一段时间内完成一段距离的滑动,在这段时间内不断地通过回调去调用 View#scrollTo 方法,从而实现弹性滑动效果。

以上是个人对 Scroller 的学习笔记,有任何不对之处望大家批评指正

1 0