如果想把一个View移动到(x, y)位置,我们通过调用scrollTo() 或者scrollBy()方法就能实现,但是在实际中我们往往希望View平滑移动,此时Scroller就派上了用场。
Scroller.java
1.
public class Scroller {
private int mStartX;//起始x坐标
private int mStartY;//起始y坐标
private int mCurrX;//当前x坐标
private int mCurrY;//当前y坐标
private boolean mFinished;//是否结束
/**
* Returns the current X offset in the scroll.
*
* @return The new X offset as an absolute distance from the origin.
*/
public final int getCurrX() {
return mCurrX;
}
/**
* Returns the current Y offset in the scroll.
*
* @return The new Y offset as an absolute distance from the origin.
*/
public final int getCurrY() {
return mCurrY;
}
/**
*
* Returns whether the scroller has finished scrolling.
*
* @return True if the scroller has finished scrolling, false otherwise.
*/
public final boolean isFinished() {
return mFinished;
}
/**
* Force the finished field to a particular value.
* 强制结束
* @param finished The new finished value.
*/
public final void forceFinished(boolean finished) {
mFinished = finished;
}
/**
* Start scrolling by providing a starting point and the distance to travel.
* The scroll will use the default value of 250 milliseconds for the
* duration.
*
* @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.
*/
public void startScroll(int startX, int startY, int dx, int dy) {
startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
}
/**
* Start scrolling by providing a starting point and the distance to travel.
* 开始平滑移动,由(startX, startY)位置在duration时间内移动(dx, dy)个单位
* @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;
}
/**
* Call this when you want to know the new location. If it returns true,
* the animation is not yet finished. loc will be altered to provide the
* new location.
* 计算当前平滑移动的位置,保存在mCurrX, mCurrY中
* 返回结果 true:平滑移动未结束 false:平滑移动结束
*/
public boolean computeScrollOffset() {
if (mFinished) {
return false;
}
int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
if (timePassed < mDuration) {
switch (mMode) {
case SCROLL_MODE:
float x = timePassed * mDurationReciprocal;
if (mInterpolator == null)
x = viscousFluid(x);
else
x = mInterpolator.getInterpolation(x);
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);
final float t_inf = (float) index / NB_SAMPLES;
final float t_sup = (float) (index + 1) / NB_SAMPLES;
final float d_inf = SPLINE[index];
final float d_sup = SPLINE[index + 1];
final float distanceCoef = d_inf + (t - t_inf) / (t_sup - t_inf) * (d_sup - d_inf);
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;
}
Scroller会帮我们计算duration时间段内任意时刻布局将要被移动到的x,y位置,比较重要的两个方法:
public void startScroll(int startX, int startY, int dx, int dy, int duration)
开始平滑移动,从(startX, startY)位置在duration时间内移动(dx, dy)个单位
public boolean computeScrollOffset()
计算当前时刻布局移动后的x,y位置,保存在mCurrX, mCurrY中,我们只有直接使用mCurrX, mCurrY即可。
2.
还有一个非常重要的方法,
View.java
/**
* 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() {
}
此方法用来移动当前布局。
/**
* {@inheritDoc}
*/
@Override
protected void dispatchDraw(Canvas canvas) {
final int count = mChildrenCount;
final View[] children = mChildren;
int flags = mGroupFlags;
....
if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {
for (int i = 0; i < count; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
} else {
for (int i = 0; i < count; i++) {
final View child = children[getChildDrawingOrder(count, i)];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
}
.....
/**
* Draw one child of this View Group. This method is responsible for getting
* the canvas in the right state. This includes clipping, translating so
* that the child's scrolled origin is at 0, 0, and applying any animation
* transformations.
*
* @param canvas The canvas on which to draw the child
* @param child Who to draw
* @param drawingTime The time at which draw is occuring
* @return True if an invalidate() was issued
*/
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
boolean more = false;
final int cl = child.mLeft;
final int ct = child.mTop;
final int cr = child.mRight;
final int cb = child.mBottom;
......
if (hardwareAccelerated) {
// Clear INVALIDATED flag to allow invalidation to occur during rendering, but
// retain the flag's value temporarily in the mRecreateDisplayList flag
child.mRecreateDisplayList = (child.mPrivateFlags & INVALIDATED) == INVALIDATED;
child.mPrivateFlags &= ~INVALIDATED;
}
child.computeScroll();
.....
}
ViewGroup绘制的时候,绘制方法dispathDraw中调用了drawChild方法,drawChild方法中又调用了View.java的computeScroll()方法。
由此设想,在view绘制过程中调用computeScroll(),如果我们在computeScroll()中通过scrollTo将view移动到我们想要的位置(即Scroller中的mCurrX,mCurry位置),不停绘制,不停移动,就能达到平滑移动的效果。
怎么控制循环调用computeScroll直到移动到我们想要的位置呢?
@Override
public void computeScroll() {
if(mScroller.computeScrollOffset()){//判断平滑移动是否结束,并且计算当前移动到的位置,保存在mCurrX, mCurrY中
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());//移动
postInvalidate();//重绘
}
}
关键是computeScroll()实现,如上:computeScroll方法通过computeScrollOffset计算当前时刻应该移动到的位置,scrollTo将布局移动到此位置,并调用重绘,重绘时又会调用computeScroll方法,直至mScroller.computeScrollOffset()方法返回true,即平滑移动结束。