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)实现滚动,而不用使用动画。