先了解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在一个时间间隔内进行滑动,也就是一个小幅度的滑动,如此反复,多次的小幅度滑动就组成了弹性滑动。