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 的学习笔记,有任何不对之处望大家批评指正

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容