Scroller的工作原理

先了解Scroller的使用:

private Scroller mScroller = new Scroller(mContext);

/**
 * 缓慢移动到指定位置
 * @param destX 指定位置x坐标
 * @param destY 指定位置y坐标
 */
private void smoothScrollTo(int destX, int destY) {
    int scrollerX = getScrollX();
    int deltaX = destX - scrollerX;
    // 1000ms内滑向destX
    mScroller.startScroll(scrollerX,0,deltaX,0,1000);
    invalidate();
}

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

Scroller的工作原理分析:
可以看到缓慢滑动的方法smothScrollTo方法中在确定结束和初始坐标后,调用了方法startScroller,那么滑动是不是就是在这个方法里面进行的呢?我们看一看该方法的源码:

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

看到这里怎么全是一些赋值操作啊,什么也没干啊,就只是保存了我们传递的一些参数。startX和startY是滑动的起点,dx和dy表示滑动的距离,duration表示的是滑动的时间,注意这里的滑动都是指的View内容的滑动而非View本身。看来滑动的操作并不是在这个方法中实现的啊,那继续往下看,就只有一个方法了,invalidate().难道是在这个方法里面实现的?不会吧!这不是重绘方法么。现在看来就只能有这一种可能了。这是怎么回事呢?

可以看到Scroller的使用中重写了computeScroll这个方法,为什么要重写这个方法呢?我们先找找这个方法都在哪里实现了。看下面:

/**
 * This method is called by ViewGroup.drawChild() to have each child view draw itself.
 *
 * This is where the View specializes rendering behavior based on layer type,
 * and hardware acceleration.
 */
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
   ...

    int sx = 0;
    int sy = 0;
    if (!drawingWithRenderNode) {
        computeScroll();
        sx = mScrollX;
        sy = mScrollY;
    }    
    ...

在draw方法中实现了!我们都知道,invalidate方法会导致View重绘,而重绘就是会去调用draw方法,好像有点联系了。我们再去看看computeScroll方法是怎么实现的:

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

这里面是一个空实现啊,我们的代码已经实现了computeScroll方法,就去看看我们重写的内容。

可以看到,方法中通过Scroller对象获取了当前的scrollX和scrollY,然后调用了scrollTo方法去进行滑动,原来最终的滑动操作就是这个!接下来调用了postInvalidate方法进行第二次重绘。和invalidate一样又会去调用draw方法,当然接着调用computeScroll,继续获取Scroller对象当前的scrollX和scrollY,并通过scrollTo方法滑动到新的位置,这样一直反复知道整个滑动过程结束。

这里面还有一个computeScrollOffset方法,看看它的实现:

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

可以大概看出这个方法中在计算当前的scrollX和scrollY,返回true表示滑动还未结束,然后计算下一次的scrollX和scrollY,直到返回false表示滑动已经结束。

通过以上的分析,我们应该已经明白了Scroller的工作原理了:Scroller配合View的computeScroll方法一同使用,它不断让View重绘,每一次重绘都距起始时间有一个时间间隔,通过这个时间间隔获取View当前的滑动位置,然后通过scrollTo方法进行滑动,每一次重绘都使View在一个时间间隔内进行滑动,也就是一个小幅度的滑动,如此反复,多次的小幅度滑动就组成了弹性滑动。

你可能感兴趣的:(View相关)