android_Scroller 弹性滑动源码分析

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绘制的时候调用
@Override
public 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 的学习笔记,有任何不对之处望大家批评指正

你可能感兴趣的:(android_Scroller 弹性滑动源码分析)