Scroller主要用在View的弹性滑动上。
Scroller用法主要分为下面三步:
1.
Scroller scroller = new Scroller(mContext);
2.
/**
* @param startX 起始坐标x
* @param startY 起始坐标y
* @param dx x坐标的变化量,如果dx>0,那么View的内容向左滑动
* @param dy y坐标的变化量,如果dy>0,那么View的内容向上滑动
* @param duration 滑动时间(ms)
*/
private void smoothScroll(int startX, int startY, int dx, int dy, int duration)
{
mScroller.startScroll(startX, startY, dx, dy, duration);
invalidate();
}
3.
@Override
public void computeScroll()
{
if(mScroller.computeScrollOffset())
{
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
概括来说,三步分别为:1. 初始化Scroller;2. 在UI主线程中,调用startScroll和invalidate();3. 重写View类中的public void computeScroll()方法。通过mScroller.computeScrollOffset()计算出最新的滑动位置,根据其返回值得知,滑动是否结束。若滑动未结束,则调用scrollTo到计算出来的滑动位置,并且调用postInvalidate()方法。
主要原理分析的话,需要分析一下Scroller源码中的startScroll()方法和computeScrollOffset()方法。
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()方法主要是初始化一些参数。具体每个参数含义,已经在之前分析过,故不再累述。
/**
* 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;
}
computeScrollOffset主要工作就是计算当前时间点View内容的x坐标和y坐标。其中,mInterpolator默认使用的是ViscousFluidInterpolator。ViscousFluidInterpolator implements Interpolator ,是Scroller.java中的一个静态类。Viscous的意思是粘性的,Fluid的意思是液体的。ViscousFluidInterpolator就是指一种类似粘性液体的Interpolator。
最后,还想再分析一下,整个View滑动的流程:a. startScroll()给mScroller传递了滑动的相关参数,调用invalidate()会触发的重绘。b. 在View重绘的时候,View的computeScroll()方法会被调用。通过调用mScroller.computeScrollOffset()计算当前View内容的位置,并通过返回值得知,滑动是否结束。若滑动未结束,调用scrollTo()方法使View的内容滑动到刚才计算出的位置,并调用postInvalidate()继续触发重绘流程。c. 步骤b不断被重复,直到mScroller.computeScrollOffset()返回true,即滑动结束为止。