我们知道想把一个View偏移至指定坐标(x,y)处,利用scrollTo()方法直接调用就OK了,但我们不能忽视的是,该方法本身 来的的副作用:非常迅速的将View/ViewGroup偏移至目标点,而没有对这个偏移过程有任何控制,对用户而言可能是不太 友好的。于是,基于这种偏移控制,Scroller类被设计出来了,该类的主要作用是为偏移过程制定一定的控制流程, 从而使偏移更流畅,更完美。

Scroller类介绍

我们就分析下源码里去看看Scroller类的相关方法. 其源代码(部分)如下: 路径位于 \frameworks\base\core\java\android\widget\Scroller.java

public class Scroller  {
    private int mStartX;    //起始坐标点 ,  X轴方向
    private int mStartY;    //起始坐标点 ,  Y轴方向
    private int mCurrX;     //当前坐标点  X轴, 即调用startScroll函数后,经过一定时间所达到的值
    private int mCurrY;     //当前坐标点  Y轴, 即调用startScroll函数后,经过一定时间所达到的值
    private float mDeltaX;  //应该继续滑动的距离, X轴方向
    private float mDeltaY;  //应该继续滑动的距离, Y轴方向
    private boolean mFinished;  //是否已经完成本次滑动操作, 如果完成则为 true
    //构造函数
    public Scroller(Context context) {
        this(context, null);
    }
    public final boolean isFinished() {
        return mFinished;
    }
    //强制结束本次滑屏操作
    public final void forceFinished(boolean finished) {
        mFinished = finished;
    }
    public final int getCurrX() {
        return mCurrX;
    }
     /* 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值中
    public boolean computeScrollOffset() {
        if (mFinished) {  //已经完成了本次动画控制,直接返回为false
            return false;
        }
        int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
        if (timePassed < mDuration) {
            switch (mMode) {
            case SCROLL_MODE:
                float x = (float)timePassed * mDurationReciprocal;
                ...
                mCurrX = mStartX + Math.round(x * mDeltaX);
                mCurrY = mStartY + Math.round(x * mDeltaY);
                break;
            ...
        }
        else {
            mCurrX = mFinalX;
            mCurrY = mFinalY;
            mFinished = true;
        }
        return true;
    }
    //开始一个动画控制,由(startX , startY)在duration时间内前进(dx,dy)个单位,即到达坐标为(startX+dx , startY+dy)出
    public void startScroll(int startX, int startY, int dx, int dy, int duration) {
        mFinished = false;
        mDuration = duration;
        mStartTime = AnimationUtils.currentAnimationTimeMillis();
        mStartX = startX;       mStartY = startY;
        mFinalX = startX + dx;  mFinalY = startY + dy;
        mDeltaX = dx;            mDeltaY = dy;
        ...
    }
}
    

其中比较重要的几个方法为:

1.构造函数:public Scroller (Context context)

使用缺省的持续持续时间和动画插入器创建一个Scroller。interpolator这里翻译为动画插入器.

2.构造函数:public Scroller (Context context, Interpolator interpolator)

根据指定的动画插入器创建一个Scroller,如果指定的动画插入器为空,则会使用缺省的动画插入器(粘滞viscous)创建。

3.public boolean computeScrollOffset()

函数功能说明:根据当前已经消逝的时间计算当前的坐标点,保存在mCurrX和mCurrY值中。 当想要知道新的位置时,调用此函数。如果返回true,表示动画还没有结束。

4.public void startScroll(int startX, int startY, int dx, int dy, int duration)

以提供的起始点和将要滑动的距离开始滚动。 函数功能说明:开始一个动画控制,由(startX , startY)在duration时间内前进(dx,dy)个单位,到达坐标为(startX+dx , startY+dy)处。

5.public void abortAnimation ()

停止动画。Scroller滚动到最终x与y位置时中止动画。

6.public final int getCurrX () /  public final int getCurrY ()

返回当前滚动X(Y)方向的偏移,返回值为距离原点X(Y)方向的绝对值

7.public final int getDuration ()

返回滚动事件的持续时间,以毫秒计算。

8.public final int getStartX () / public final int getStartY ()

返回滚动起始点的X(Y)方向的偏移,返回值为起始点在X(Y)方向距离原点的绝对距离。

9.public final boolean isFinished ()

返回scroller是否已完成滚动。

10.public int timePassed ()

返回自滚动开始经过的时间,经过时间以毫秒为单位.

computeScroll()方法介绍

为了易于控制滑屏控制,Android框架提供了 computeScroll()方法去控制这个流程。在绘制View时,会在draw()过程调用该 方法。因此, 再配合使用Scroller实例,我们就可以获得当前应该的偏移坐标,手动使View/ViewGroup偏移至该处。 computeScroll()方法原型如下,该方法位于ViewGroup.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.
     */由父视图调用用来请求子视图根据偏移值 mScrollX,mScrollY重新绘制
    public void computeScroll() { //空方法 ,自定义ViewGroup必须实现方法体
    }
    

为了实现偏移控制,一般自定义View/ViewGroup都需要重载该方法 。 其调用过程位于View绘制流程draw()过程中,如下:

@Override
protected void dispatchDraw(Canvas canvas){
    ...
    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);
        }
    }
}
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    ...
    child.computeScroll();
    ...
}
    

ViewConfiguration类

功能: 获得一些关于timeouts(时间)、sizes(大小)、distances(距离)的标准常量值 。

常用方法:

public int getScaledEdgeSlop

说明:获得一个触摸移动的最小像素值。也就是说,只有超过了这个值,才代表我们该滑屏处理了

public static int getLongPressTimeout()

说明:获得一个执行长按事件监听(onLongClickListener)的值。也就是说,对某个View按下触摸时,只有超过了 这个时间值在,才表示我们该对该View回调长按事件了;否则,小于这个时间点松开手指,只执行onClick监听。

public int getScaledTouchSlop()

说明:返回一个距离,表示滑动的时候,手的移动要大于这个距离才开始移动控件。

调用方法:

ViewConfiguration.get(getContext()).getScaledTouchSlop();

VelocityTracker类

功能: 根据触摸位置计算每像素的移动速率。

常用方法有:

public void addMovement (MotionEvent ev)

功能:添加触摸对象MotionEvent , 用于计算触摸速率。

public void computeCurrentVelocity (int units)

功能:以每像素units单位考核移动速率。

public float getXVelocity ()

功能:获得X轴方向的移动速率。

用法:一般在onTouchEvent事件中被调用,先在down事件中获取一个VecolityTracker对象,然后在move或up事件中获取速度,调用流程可如下列所示:

VelocityTracker vTracker = null
@Override
public boolean onTouchEvent(MotionEvent event){
    int action = event.getAction();
    switch(action){
    case MotionEvent.ACTION_DOWN:
        if(vTracker == null){
            vTracker = VelocityTracker.obtain();
        }else{
            vTracker.clear();
        }
        vTracker.addMovement(event);
        break;
    case MotionEvent.ACTION_MOVE:
        vTracker.addMovement(event);
        //设置单位,1000 表示每秒多少像素(pix/second),1代表每微秒多少像素(pix/millisecond)。
        vTracker.computeCurrentVelocity(1000);
        //从左向右划返回正数,从右向左划返回负数
        System.out.println("the x velocity is "+vTracker.getXVelocity());
        //从上往下划返回正数,从下往上划返回负数
        System.out.println("the y velocity is "+vTracker.getYVelocity());
        break;
    case MotionEvent.ACTION_UP:
    case MotionEvent.ACTION_CANCEL:
        vTracker.recycle();
        break;
    }
    return true;
}
        

那么获得滑动速度有什么用呢,假如我们做一个滑动翻页的相册,那么可以设置一个常量,表示滑动最快速度。超过了这个速度则实现翻页效果, 就可以使用上面的方法。这个速度是有正负之分的,所以可以用来区别滑动的方向。