Android进阶二十一 Scroller使用总结

Scroller是一个用于实现弹性滑动的帮助类,它主要是通过时间的流逝,根据设置到Scroller中的插值器,计算出当前滑动到的距离,然后我们根据scrollTo/scrollBy去让view产生弹性滑动的动画效果,与属性动画类似,使用Scroller要明白一点,那就是:

Scroller只能对View中的内容进行滑动,如一个Button使用Scroller以后,Button的背景不会滑动,而是Button中的title滑动,如一个ViewGroup使用Scroller以后,ViewGroup不会滑动,而是ViewGroup中的子View滑动

scrollTo/scrollBy

分析Scroller之前先来看下scrollTo/scrollBy这两个方法:

public void scrollTo(int x, int y) {
    if (mScrollX != x || mScrollY != y) {
        int oldX = mScrollX;
        int oldY = mScrollY;
        mScrollX = x;
        mScrollY = y;
        invalidateParentCaches();
        onScrollChanged(mScrollX, mScrollY, oldX, oldY);
        if (!awakenScrollBars()) {
            postInvalidateOnAnimation();
        }
    }
}
public void scrollBy(int x, int y) {
    scrollTo(mScrollX + x, mScrollY + y);
}

scrollTo是实现基于传递参数的绝对滑动,scrollBy调用了scrollTo,是基于传递参数的相对滑动。

scrollTo主要有两个参数mScrollX和mScrollY,单位为像素,我们需要明白这两个属性的变化规则,这两个属性可以通过getScrollX和getScrollY获得,在滑动过程中mScrollX的值总是等于View的左边框和View中内容左边框在水平方向上的距离,mScrollY的值总是等于View的上边框和View中内容上边框在垂直方向上的距离(再次强调,scrollTo/scrollBy只能改变view中内容或者子view的位置而不能改变view在父布局中的位置),当view的左边缘在view内容左边缘的右边的时候,mScrollX为正直,反之为负值,换句话说,就是从右向左滑动的时候,mScrollX为正值,反之为负值,mScrollY也是同理。以图为例(黑色边框部分为view,灰色部分为view中的内容或子view):

这里写图片描述
mScrollX = 100

这里写图片描述
mScrollX = -100

这里写图片描述
mScrollY = 100

这里写图片描述
mScrollY = -100

Scroller

使用Scroller步骤如下:

1.重写View或者ViewGroup

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

    public ScrollerLayout(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public ScrollerLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mContext = context;
        scrollerInit();
    }

2.重写View的computeScroll()方法

@Override
public void computeScroll() {
    super.computeScroll();
    if (mScroller.computeScrollOffset()) {
        scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
        postInvalidate();
    }
}

3.写一个方法,传入滑动距离参数

主要调用下面两个方法:

mScroller.startScroll(scrollX,scrollY,deltaX,deltaY,3000);
invalidate();

方法:

  public void smoothScrollTo(int destX,int destY)
  {
        int scrollX = getScrollX();
        int deltaX = destX - scrollX;

        int scrollY = getScrollY();
        int deltaY = destY - scrollY;
        mScroller.startScroll(scrollX,scrollY,deltaX,deltaY,3000);
        invalidate();
  }

这样获得ScrollerLayout对象以后,调用smoothScrollTo方法,传入距离参数就可以实现ScrollerLayout中子view的弹性滑动。

源码分析

1.构造器
先来看构造函数,要实现弹性滑动,需要有插值器,来看Scroller的构造函数,Scroller有三个构造器,但是最终都会调用下面这个:

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
}

interpolator参数可以外面传入,如果未设置,则默认为ViscousFluidInterpolator。

2.startScroll

我们再来看看Scroller的startScroll():

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

startScroll方法并未开始滑动,只是初始化了一些参数,那何时开始滑动呢?

我们在startScroll后有调用invalidate方法,这个方法通知view去重绘,view重绘会调用我们重写的computeScroll方法,computeScroll方法中调用了Scroller的computeScrollOffset方法根据时间和插值器的值计算当前view应该滑动到的位置,然后调用view的scrollTo方法对view进行滑动。

3.computeScrollOffset
这个是核心方法,根据时间和插值器的值计算当前view应该滑动到的位置:

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

这个方法的返回值有讲究,若返回true则说明Scroller的滑动没有结束;若返回false说明Scroller的滑动结束了。再来看看内部的代码:先是计算出了已经滑动的时间,若已经滑动的时间小于总滑动的时间,则说明滑动没有结束;不然就说明滑动结束了,设置标记mFinished = true;。而在滑动未结束里面又分为了两个mode,不过这两个mode都干了差不多的事,大致就是根据刚才的时间timePassed和插补器来计算出该时间点滚动的距离mCurrX和mCurrY。也就是上面的mScroller.getCurrX(), mScroller.getCurrY()的值。

然后再调用scrollTo()方法滚动到指定点(即上面的mCurrX, mCurrY)。之后又调用了postInvalidate();,让View重绘并重新调用computeScroll()以此循环下去,一直到View滚动到指定位置为止,至此Scroller滚动结束。

其实Scroller的原理还是比较通俗易懂的。我们再来理清一下思路,以一张图的形式来终结今天的Scroller解析:

这里写图片描述

猜你喜欢

转载自blog.csdn.net/lixpjita39/article/details/79344653