Scroller 和 ScrollTo ScrollBy的结合使用

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

getScrollX, Return the scrolled left position of this view. This is the left edge of the displayed part of your view,in pixels.

view实际内容占的大小(画布上画的内容)可能远远超过其显示区域的大小(view布局大小),ScrollX,是当前显示区域的左边界在画布坐标系中的位置。

画布坐标系也是左上角为原点

getScrollY, 同上是显示区域的上边界在画布坐标系中的位置。

scrollBy,scrollTo,移动画布,在画布坐标系上移动x,y轴位置,调整显示区域。

scrollBy(20)相当于画布向左移动了20px

scrollX 加正数表示向左移动

scrollY加正数表示向上移动

模拟Scroller类的实现功能:

      假设从上海做动车到武汉需要10个小时,行进距离为1000km ,火车速率200/h 。采用第一种时间控制方法到达武汉的

   整个配合过程可能如下:

        我们每隔一段时间(例如1小时),计算火车应该行进的距离,然后调用scrollTo()方法,行进至该处。10小时过完后,

    我们也就达到了目的地了。

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;  
        ...  
    }  
} 

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

           public boolean computeScrollOffset()

函数功能说明:根据当前已经消逝的时间计算当前的坐标点,保存在mCurrX和mCurrY值中

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

 函数功能说明:开始一个动画控制,由(startX , startY)在duration时间内前进(dx,dy)个单位,到达坐标为 (startX+dx , startY+dy)处。

computeScroll()方法介绍

      为了易于控制滑屏控制,Android框架提供了 computeScroll()方法去控制这个流程。在绘制View时,会在draw()过程调用该

  方法。因此, 再配合使用Scroller实例,我们就可以获得当前应该的偏移坐标,手动使View/ViewGroup偏移至该处。该方法位于ViewGroup.java类中      

第一、调用Scroller实例去产生一个偏移控制(对应于startScroll()方法),手动调用invalid()方法去重新绘制。

第二、ViewGroup重绘时会调用computeScroll(), 剩下的就是在 computeScroll()里根据当前已经逝去的时间,获取当前 应该偏移的坐标(由Scroller实例对应的computeScrollOffset()计算而得),

第三、当前应该偏移的坐标,调用scrollBy()方法去缓慢移动至该坐标处。

 

public void startMove() {
        // 使用动画控制偏移过程 , 3s内到位
        mScroller.startScroll((curScreen - 1) * getWidth(), 0, getWidth(), 0, 3000);
        // 其实点击按钮的时候,系统会自动重新绘制View,我们还是手动加上吧。
        invalidate();
        // 使用scrollTo一步到位
        // scrollTo(curScreen * MultiScreenActivity.screenWidth, 0);
}

重写ViewGoup的computeScroll方法:

    @Override
    public void computeScroll() {
        // 如果返回true,表示动画还没有结束
        // 因为前面startScroll,所以只有在startScroll完成时 才会为false
        if (mScroller.computeScrollOffset()) {// 产生了动画效果,根据当前值 每次滚动一点
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());// 此时同样也需要刷新View ,否则效果可能有误差
            postInvalidate();
        } 
    }

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

finish状态后,getCurrX得到的值是   startX + dx,

原理即根据插值器和duration,将dx的值分成小片加到startX上,同理getCurrY

 

 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

 一个替代依靠invalide 和 computeScroll 来使用 Scroller的方法:

private class FlingRunnable implements Runnable {

        private Scroller mScroller;

        public FlingRunnable() {
            mScroller = new Scroller(getContext());
        }

        public void startScroll(int distance) {
            //mScroller.startScroll(0, distance, 0, -distance, 1000);
            //调用scroller的方法
            
            
            post(this);
        }

        public void stop() {
            removeCallbacks(this);
            mScroller.forceFinished(true);
        }

        public void run() {
            final Scroller scroller = mScroller;
            boolean more = scroller.computeScrollOffset();
            

            if (more) {
                // 在这里使用Scroller的x,y值

                post(this);
            } else {
                stop();
            }
        }

    }
原文地址:https://www.cnblogs.com/zijianlu/p/2506495.html