Android源码解析Scroller

大家知道,scroller可以进行滑动,但是他的使用流程和一般类的使用不太一样。为了更好的理解,我们需要学习下Scroller的源码。大家还记得怎么去使用Scroller吗?来回顾一下:

系统会在绘制View的时候,在draw()方法中,调用computeScroller方法。我们通过调用view.getParent的viewGroup的scrollTo方法。来获取当前滑动的值。但是这个当前滑动的指怎么去获取呢?我们创建一个scroller变量。然后通过调用scroller.startScroller方法。来使之产生。来回忆下代码:

  private val mScroller = Scroller(context)

     fun smoothScrollTo(destX:Int, destY:Int){
        val scrollX = scrollX
        val delta  = destX - scrollX
        mScroller.startScroll(scrollX,0,delta,2,2000)
        invalidate()
    }

    override fun computeScroll() {
        super.computeScroll()
        if (mScroller.computeScrollOffset()) {
            (parent as View).scrollTo(mScroller.currX,mScroller.currY)
            invalidate()
        }
    }

这是我们的自定义View里的方法,这里是smoothScrollerTo是提供给外部的方法。

我们先来看下Scroller(context)构造方法:

 public Scroller(Context context) {
        this(context, null);
    }

    /**
     * Create a Scroller with the specified interpolator. If the interpolator is
     * null, the default (viscous) interpolator will be used. "Flywheel" behavior will
     * be in effect for apps targeting Honeycomb or newer.
     */
    public Scroller(Context context, Interpolator interpolator) {
        this(context, interpolator,
                context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB);
    }

    /**
     * Create a Scroller with the specified interpolator. If the interpolator is
     * null, the default (viscous) interpolator will be used. Specify whether or
     * not to support progressive "flywheel" behavior in flinging.
     */
    public Scroller(Context context, Interpolator interpolator, boolean flywheel) {
        mFinished = true;
        if (interpolator == null) {
            mInterpolator = new ViscousFluidInterpolator();
        } else {
            mInterpolator = interpolator;
        }
        mPpi = context.getResources().getDisplayMetrics().density * 160.0f;
        mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());
        mFlywheel = flywheel;

        mPhysicalCoeff = computeDeceleration(0.84f); // look and feel tuning
    }

最终我们都会调用第三个构造方法,后面两个参数,我们默认传递的一个是null,一个是false.

接下来我们看看scroller.startScroll方法。代码如下:


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

大家可以看到,在这个方法中,并没有开始滑动的方法,而是保存了传进来的各种参数。所以startScroller这个方法只是用来做前期准备的。并不能使View进行滑动。回顾上面我们提供给外部的smoothScroll方法,关键是在调用startScroll之后,我们调用了invalidate方法,这个方法会导致view进行重绘。而View的重绘会调用View的draw方法,draw方法又会调用View的computeScroll方法。在scroller中如何获取当前位置的scrollx和scrollY呢?因为我们在调用scrollTo方法会调用scroller的computeScrollOffset方法。

  public boolean computeScrollOffset() {
        if (mFinished) {
            return false;
        }

        int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
    
        if (timePassed < mDuration) {
            switch (mMode) {
            case SCROLL_MODE:
                final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
                mCurrX = mStartX + Math.round(x * mDeltaX);
                mCurrY = mStartY + Math.round(x * mDeltaY);
                break;
            case FLING_MODE:
                final float t = (float) timePassed / mDuration;
                final int index = (int) (NB_SAMPLES * t);
                float distanceCoef = 1.f;
                float velocityCoef = 0.f;
                if (index < NB_SAMPLES) {
                    final float t_inf = (float) index / NB_SAMPLES;
                    final float t_sup = (float) (index + 1) / NB_SAMPLES;
                    final float d_inf = SPLINE_POSITION[index];
                    final float d_sup = SPLINE_POSITION[index + 1];
                    velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
                    distanceCoef = d_inf + (t - t_inf) * velocityCoef;
                }

                mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;
                
                mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
                // Pin to mMinX <= mCurrX <= mMaxX
                mCurrX = Math.min(mCurrX, mMaxX);
                mCurrX = Math.max(mCurrX, mMinX);
                
                mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
                // Pin to mMinY <= mCurrY <= mMaxY
                mCurrY = Math.min(mCurrY, mMaxY);
                mCurrY = Math.max(mCurrY, mMinY);

                if (mCurrX == mFinalX && mCurrY == mFinalY) {
                    mFinished = true;
                }

                break;
            }
        }
        else {
            mCurrX = mFinalX;
            mCurrY = mFinalY;
            mFinished = true;
        }
        return true;
    }

我们来分析这个方法,首先计算动画持续的时间timePassed.如果动画持续的时间小于我们设置的滑动持续时间mDuration,则执行switch语句。因为在startScroll方法中的mMode值SCROLL_MODE,所以执行分支语句SCROLL_MODE,然后根据插值器来计算出在该时间段内移动的距离。赋值给mCurX和mCurrY,这样我们就能通过Scroller来获取当前的srollX和scrollY了。另外,computeScrollOffset的返回值如果为true,则表示滑动未结束。如果为false,则表示滑动结束。所以,如果滑动未结束,我们就得持续调用scrollTo方法和invalidate方法来进行View的滑动。

所以,我们总结一下Scoller的原理:

Scroller并不能直接实现View的滑动,它需要配合View的computeScroll方法。在computeScroll方法中不断让view进行重绘,每次重绘都会计算滑动持续的时间,根据这个持续时间就能算出这个View滑动 的位置。我们根据每次滑动的位置调用scrollTo方法进行滑动,这样不断地重复上次过程就行程了弹性滑动。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值