Scroller类,改变mScrollX和mScrollY值,实现视图内部平缓偏移。View的scrollTo和scrollBy方法也可以改变Scroll值,但它们一瞬间完成,体验不好。ViewPager和ScrollView的自动滚动效果都是通过它实现的。本文介绍它的实现原理。
在视图内部定义一个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;//X方向开始位置
mStartY = startY;//Y方向开始位置
mFinalX = startX + dx;//X方向结束的位置
mFinalY = startY + dy;//Y方向结束的位置
mDeltaX = dx;//X方法偏移的距离
mDeltaY = dy;//Y方向偏移的距离
mDurationReciprocal = 1.0f / (float) mDuration;
}
该方法有五个参数,默认duration是250毫秒,startX和startY是开始位置,dx和dy是偏移量,mStartTime初始化起始时间。根据起始位置和偏移计算结束位置。
dy正值,内部视图整体向上移动。
dy负值,内部视图整体向下移动。
从上面源码可以看出,该方法仅仅是初始化,在Scroller对象记录一些的值而已。调用后一定要invalidate方法视图刷新。
实现滚动的视图重写View的computeScroll方法。在invalidate方法刷新时,会回调到该方法中。
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();//继续刷新UI。
}
super.computeScroll();
}
它在View类的源码中是空方法,视图渲染时触发,不重写无法实现滚动效果,Scroller的computeScrollOffset方法,计算Scroller当前偏移量。
public boolean computeScrollOffset() {
if (mFinished) {//每次startScroll时,设置false。
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:
//Fling效果
}
}
else {
mCurrX = mFinalX;
mCurrY = mFinalY;
mFinished = true;
}
return true;
}
如果滚动已经结束,退出该方法,根据起始时间和当前时间,以及速率控制拦截器,计算此刻应该所在的位置mCurrX和mCurrY。scrollTo方法,滚动到该位置,postInvalidate方法继续刷新。刷新中继续回调computeScroll方法。因此,实现在一定时间内平滑滚动一定距离。
如果已经到达持续时间,将当前位置设置成结束位置,mFinished结束标志,下一次computeScrollOffset方法直接返回,computeScroll方法将不再滚动和刷新。
逻辑和补间动画实现原理类似。
总结
Scroller原理比较简单,重写View基类的computeScroll方法。
startScroll方法后,一定要刷新视图,否则不会出现滚动效果。
任重而道远
网友评论