1.Scroller是什么?
Scroller本身不会去移动View,它是一个移动计算辅助类,用于跟踪控件滑动的轨迹,只相当于一个滚动轨迹的记录工具,最终还是通过View的scrollTo,scrollBy方法完成View的移动;
2.Scroller源码分析,如何实现滚动轨迹记录的呢?
再讲源码之前先了解两个重要的方法:
1)startScroll()
public void startScroll(int startX, int startY, int dx, int dy, int duration) {}
开始一个动画控制,由(startX,startY)在duration时间内前进(dx,dy)个单位,即达到偏移坐标为(startX+dx,startY+dy)
2)computeScrollOffset()
public boolean computeScrollOffset()
滑动过程中,根据当前已经消逝的时间计算当前偏移的坐标点,保存在mCurrX和mCurrY值中。
上面两个方法的源码如下:
public class Scroller {
private int mStartX; //水平方向,滑动时的起点偏移坐标
private int mStartY; //垂直方向,滑动时的起点偏移坐标
private int mFinalX; //滑动完成后的偏移坐标,水平方向
private int mFinalY; //滑动完成后的偏移坐标,垂直方向
private int mCurrX; //滑动过程中,根据消耗时间计算出的当前的滑动偏移距离,水平方向
private int mCurrY; //滑动过程中,根据消耗时间计算出的当前的滑动偏移距离,垂直方向
private int mDuration; //本次滑动的动画时间
private float mDeltaX; //滑动过程中,在达到mFinalX前还需要滑动的距离,水平方向
private float mDeltaY; //滑动过程中,在达到mFinalX前还需要滑动的距离,垂直方向
public void startScroll(int startX, int startY, int dx, int dy) {
startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
}
/**
* 开始一个动画控制,由(startX,startY)在duration时间内前进(dx,dy)个单位,即达到偏移坐标为(startX+dx,startY+dy)
*/
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;
}
/**
* 滑动过程中,根据当前已经消耗的时间计算当前偏移的坐标点,保存在mCurrX和mCurrY值中
*/
public boolean computeScrollOffset() {
if (mFinished) { //已经完成了本次动画控制,直接返回为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);//计算出当前滑动的偏移位置,x轴
mCurrY = mStartY + Math.round(x * mDeltaY);//计算出当前滑动的偏移位置,y轴
break;
...
}
}
else {
mCurrX = mFinalX;
mCurrY = mFinalY;
mFinished = true;
}
return true;
}
}
Scroller类中最重要的两个方法就是startScroll()和computeScrollOffset(),当时Scroller类只是一个滑动计算辅助类,它的startScroll()和computeScrollOffset()方法中也只是对一些轨迹参数进行设置和计算,真正需要进行滑动还的通过View的scrollTo(),scrollBy()方法。为此,View中提供了computeScroll方法来控制这个滑动流程。computeScroll()方法会在绘制子视图的时候进行调用。源码如下:
//由父视图调用用来请求子视图根据偏移值 mScrollX,mScrollY重新绘制
public void computeScroll(){} //空方法,自定义滑动功能的ViewGroup必须实现的方法体
因此Scroller类的基本使用流程可以总结如下:
(1)首先通过Scroller类的startScroll()开始一个滑动动画控制,里面进行了一些轨迹参数的设置和计算;
(2)在调用 startScroll()的后面调用invalidate();引起视图的重绘操作,从而触发ViewGroup中的computeScroll()被调用;
(3)在computeScroll()方法中,先调用Scroller类中的computeScrollOffset()方法,里面根据当前消耗时间进行轨迹坐标的计算,然后取得计算出的当前滑动的偏移坐标,调用View的scrollTo()方法进行滑动控制,最后也需要调用invalidate();进行重绘。 如下的一个简单代码示例:
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int)event.getX();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
mLastX = x;
break;
case MotionEvent.ACTION_MOVE:
int dx = mLastX - x;
int oldScrollX = getScrollX(); //原来的偏移量
int preScrollX = oldScrollX + dx; //本次滑动后的形成的偏移量
if(preScrollX<0){
preScrollX = 0;
dx = preScrollX - oldScrollX;
}
if(preScrollX>(getChildCount()-1)*getWidth()){
preScrollX = (getChildCount()-1)*getWidth();
dx = preScrollX - oldScrollX;
}
mScroller.startScroll(mScroller.getFinalX(),mScroller.getFinalY(),d