OverScroller、Scroll到edge时的阻尼效果,overScrollBy和scrollTo,AbsListView和ScrollView的滚动实现区别

OverScroller

和Scroller类似,都只是根据duration、已过去的时间,start position,final position,根据某种interpolator计算某个时刻的scrollX和scrollY(这里说的scrollX/Y和View的成员mScrollX/Y无关)。AbsListView和ScrollView添加阻尼效果使用的Scroller都是OverScroller,当然,还需其他类配合。

如果需要使用判断边界和绘制某种边界效果,使用View.overScrollBy()方法代替直接使用View.scrollTo()。而是否允许overScrollmode,则可由android:overScrollMode="true|false"来设定。

如果想要制造阻尼效果,可以参考ScrollView的实现,简单明了,AbsListView太复杂了。ScrollView使用computeScroll()驱动scroll过程的(AbsListView使用的其内部的FlingRunnable,通过不断postOnAnimation(Runnable)驱动scroll过程)。因为滚动计算和滚动变化AbsListView更复杂,所以夹杂在其中的阻尼效果代码的流程就不那么明朗,但是ListView也是使用了OverScroller,overScrollBy(),onOverScroll,EdgeEffect来实现阻尼效果的。

下面看看ScrollView#computeScroll():

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            // This is called at drawing time by ViewGroup.  We don't want to
            // re-show the scrollbars at this point, which scrollTo will do,
            // so we replicate most of scrollTo here.
            //
            //         It's a little odd to call onScrollChanged from inside the drawing.
            //
            //         It is, except when you remember that computeScroll() is used to
            //         animate scrolling. So unless we want to defer the onScrollChanged()
            //         until the end of the animated scrolling, we don't really have a
            //         choice here.
            //
            //         I agree.  The alternative, which I think would be worse, is to post
            //         something and tell the subclasses later.  This is bad because there
            //         will be a window where mScrollX/Y is different from what the app
            //         thinks it is.
            //
            int oldX = mScrollX;
            int oldY = mScrollY;
            int x = mScroller.getCurrX();
            int y = mScroller.getCurrY();

            if (oldX != x || oldY != y) {
                final int range = getScrollRange();
                final int overscrollMode = getOverScrollMode();
                final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
                        (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
//主要是计算过程滚动过程,然后回调onOverScroll()方法,overScrollBy不用复写,复写
//onOverScroll()方法。这里是使用overScrollBy代替直接使用scrollTo。在onOverScroll()
//中还是使用scrollTo
                overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
                        0, mOverflingDistance, false);
                onScrollChanged(mScrollX, mScrollY, oldX, oldY);

                if (canOverscroll) {//使用EdgeEffect绘制阻尼效果
                    if (y < 0 && oldY >= 0) {
                        mEdgeGlowTop.onAbsorb((int) mScroller.getCurrVelocity());
                    } else if (y > range && oldY <= range) {
                        mEdgeGlowBottom.onAbsorb((int) mScroller.getCurrVelocity());
                    }
                }
            }

            if (!awakenScrollBars()) {
                // Keep on drawing until the animation has finished.
                postInvalidateOnAnimation();
            }
        } else {
            if (mFlingStrictSpan != null) {
                mFlingStrictSpan.finish();
                mFlingStrictSpan = null;
            }
        }
    }

看ScrollView复写的onOverScroll():

    @Override
    protected void onOverScrolled(int scrollX, int scrollY,
            boolean clampedX, boolean clampedY) {//如果clamped为true,则代表某个方向到边界了
//到边界后,scrollX/Y会和之前的值相同
        // Treat animating scrolls differently; see #computeScroll() for why.
        if (!mScroller.isFinished()) {
            final int oldX = mScrollX;
            final int oldY = mScrollY;
            mScrollX = scrollX;
            mScrollY = scrollY;
            invalidateParentIfNeeded();
            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
            if (clampedY) {//说明到了需要overScroll并需要产生阻尼效果或其他效果,看Android版本的实现。该方法中会计算速度SplineOverScroller.mVelocity,EdgeEffect会用到这个数据
                mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange());
            }
        } else {
//如果还没有到边界,就使用scrollTo就好。所以View.overScrollBy还将scrollX/Y计算好了
            super.scrollTo(scrollX, scrollY);
        }

        awakenScrollBars();
    }

AbsListView滚动使用的变换是offsetChildrenTopAndBottom(),ScrollView使用的是scrollTo().

    public void offsetChildrenTopAndBottom(int offset) {
        final int count = mChildrenCount;
        final View[] children = mChildren;
        boolean invalidate = false;
 
        for (int i = 0; i < count; i++) {
            final View v = children[i];
            v.mTop += offset;
            v.mBottom += offset;
            if (v.mRenderNode != null) {
                invalidate = true;
                v.mRenderNode.offsetTopAndBottom(offset);
            }
        }
 
        if (invalidate) {
            invalidateViewProperty(false, false);
        }
        notifySubtreeAccessibilityStateChangedIfNeeded();}

 

ListView的滚动实现:

FlingMode:FlingRunnbale作为一次滚动单一任务,一次Fling需要很多个FlingRunnbale来完成滚动,每次执行FlingRunnbale都会通过View#postOnAnimation(),把FlingRunnbale重新放入Choreographer,dattachInfo.mViewRootImpl.mChoreographer.postCallback( Choreographer.CALLBACK_ANIMATION, action, null);通过OverScroller#computeScrollOffset()和OverScroller#getCurrY()实现获得deltaY,然后trackMotionScroll,其中调用offsetChildrenTopAndBottom(deltaY)实现滚动。

View#postOnAnimation()

/**
     * 

Causes the Runnable to execute on the next animation time step. * The runnable will be run on the user interface thread.

* * @param action The Runnable that will be executed. * * @see #postOnAnimationDelayed * @see #removeCallbacks */ public void postOnAnimation(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { attachInfo.mViewRootImpl.mChoreographer.postCallback( Choreographer.CALLBACK_ANIMATION, action, null); } else { // Postpone the runnable until we know // on which thread it needs to run. getRunQueue().post(action); } }

ScrollMode:直接使用trackMotionScroll,然后调用offsetChildrenTopAndBottom(deltaY)实现滚动,而不用使用动画。

你可能感兴趣的:(原生控件)