美文网首页安卓工具相关
Scroller实现滚动的原理

Scroller实现滚动的原理

作者: leilifengxingmw | 来源:发表于2019-07-06 17:56 被阅读10次

以前买了一本《Android 开发艺术探索》,当时看完也是感觉受益匪浅,书上面也是留下了努力学习的笔记,哈哈,结果不知道怎么搞丢了,也是艰难,最近又新买了一本,看起来还是感觉受益匪浅,哈哈。

先看一个简单的使用Scroller的例子


1562401862002342.gif

从上面的图片中也可以看出来,这里的滚动是指View内容的滚动而非View本身位置的改变。

上面例子中使用到的自定义的TestSmoothScrollView,代码如下

class TestSmoothScrollView @JvmOverloads constructor(
        context: Context,
        attrs: AttributeSet? = null,
        defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    private var scroller: Scroller = Scroller(context)
    private val paint = Paint()
    private var color: Int = 0

    init {
        color = context.resources.getColor(R.color.colorAccent)
        paint.color = color
        paint.style = Paint.Style.FILL
    }

    override fun onDraw(canvas: Canvas) {
        canvas.drawColor(color)
        paint.color = context.resources.getColor(R.color.colorPrimary)
        canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
    }

    /**
     * 使用 scroller滚动
     *
     * @param destX 在水平方滚动到的目的地
     * @param destY 竖直方向上滚动的目的地
     */
    fun smoothScrollTo(destX: Int, destY: Int) {

        //要滚动的距离
        val deltaX = destX - scrollX
        val deltaY = destY - scrollY

        scroller.startScroll(scrollX, scrollY, deltaX, deltaY, 1000)
        invalidate()
    }

    override fun computeScroll() {
        if (scroller.computeScrollOffset()) {
            scrollTo(scroller.currX, scroller.currY)
            invalidate()
        }
    }
}

调用TestSmoothScrollView的smoothScrollTo方法即可实现滚动。

btnStartScroll.setOnClickListener {
   //向右下方向滚动100像素
   smoothScrollView.smoothScrollTo(-100, -100)
}

我们先来看一下TestSmoothScrollView的smoothScrollTo方法

/**
  * 使用 scroller滚动
  *
  * @param destX 在水平方滚动到的目的地
  * @param destY 竖直方向上滚动的目的地
  */
fun smoothScrollTo(destX: Int, destY: Int) {

     //计算出要滚动的距离
     val deltaX = destX - scrollX
     val deltaY = destY - scrollY
     //注释1处
     scroller.startScroll(scrollX, scrollY, deltaX, deltaY, 1000)
     //注释2处
     invalidate()
}

首先我们根据View当前的scrollX,scrollY 和传入的参数计算出水平和竖直方向上要滚动的距离。然后在注释1处调用了Scroller的startScroll方法。

Scroller的startScroll方法

/**
 * 通过提供一个起点,滚动距离和滚动时间开始滚动。
 * 
 * @param startX 水平方向上的滚动起点,单位是像素。
 * @param startY 竖直方向上的滚动起点,单位是像素。
 * @param dx 水平滚动距离,单位是像素。正值会使View的内容向左滚动。
 * @param dy 竖直方向上的滚动距离,单位是像素。正值会使View的内容向上滚动。
 * @param 滚动时间,单位是毫秒
 */
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
   //为mMode赋值为SCROLL_MODE
    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方法中,代码很简单,只是保存了某些值。有几个比较重要的点。

  • 将mMode赋值为SCROLL_MODE
  • 为开始滚动时间mStartTime赋值
  • 计算滚动时间的倒数mDurationReciprocal

但是我们发现这里并没有让View滚动起来。那么View是怎么滚动起来的呢,答案就是TestSmoothScrollView的smoothScrollTo方法的注释2处,调用了invalidate方法。

调用invalidate以后,会导致View重绘,View在重绘过程中又会调用computeScroll方法,而computeScroll又会从Scroller中获取当前的scrollX和scrollY然后通过scrollTo方法实现滚动。接着又调用invalidate方法进行第二次重绘,如此反复直到滑动到最终的位置。

override fun computeScroll() {
    //注释1处,
    if (scroller.computeScrollOffset()) {
        //注释2处,调用scrollTo方法滚动到当前应该到达位置
        scrollTo(scroller.currX, scroller.currY)
       //继续调用invalidate方法请求重绘。
        invalidate()
    }
}

我们看下注释1处Scroller的computeScrollOffset方法

/**
 * 如果你想知道新的位置,请调用这个方法。如果该方法返回true,说明动画还没结束。
 */ 
public boolean computeScrollOffset() {
    if (mFinished) {
        return false;
    }
    //计算流逝的时间
    int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
    //如果没有结束
    if (timePassed < mDuration) {
        switch (mMode) {//注意,我们在上面为mMode赋值为SCROLL_MODE
        case SCROLL_MODE:
            //插值器根据流逝的时间计算应改变的百分比x
            final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
            mCurrX = mStartX + Math.round(x * mDeltaX);
            mCurrY = mStartY + Math.round(x * mDeltaY);
            break;
        //...
        }
    }
    else {
        //结束
        mCurrX = mFinalX;
        mCurrY = mFinalY;
        mFinished = true;
    }
    return true;
}

在上面的方法中,如果已经到了滚动的结束时间,那么滚动结束,该方法返回false。

如果时间还没有到滚动的结束时间,步骤如下:

  1. 计算流逝的时间
  2. 插值器根据流逝的时间计算应改变的百分比x
  3. 计算当前应该滚动到的位置赋值给mCurrX,mCurrY。
  4. 返回true。

如果返回了true,则View会调用scrollTo方法滚动到当前应该到达位置,然后继续调用invalidate方法请求重绘。

if (scroller.computeScrollOffset()) {
    //调用scrollTo方法滚动到当前应该到达位置currX,currY
    scrollTo(scroller.currX, scroller.currY)
   //继续调用invalidate方法请求重绘。
    invalidate()
}

整个过程就是这样,下面来一张流程图。


Scroller实现滚动的原理.jpg

参考

  1. 《Android 开发艺术探索》

相关文章

网友评论

    本文标题:Scroller实现滚动的原理

    本文链接:https://www.haomeiwen.com/subject/hgoyeqtx.html