通过回弹效果理解Scroller

1.写在前面

通过整个博客以及例子的布局回弹效果理解以及搞清楚以下几个点:
1. Scroller的使用(这里学习了谷歌的小弟 这个大神的,在他的博客中学到了很多关于Scroller知识,但是因为他水平太高,很多初级的东西没讲解,所以通过打断点以及写log弄明白很多未知的知识);
2. GestureDetector.OnGestureListener中onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)方法中参数的意义;
3. scrollTo以及scrollBy的区别以及scrollTo使用时总是感觉方向错了;
4. Scroller.getFinalY()以及Scroller.getCurrY()非常实在的意思,并不是很抽象的最后的位置以及当前位置的解释;
5. 布局回弹效果的理解;


2.干货

A. 都说scrollTo移动的并不是view而且view里面的内容,但是使用时内容总是会往相反的方向跑,这里那位大佬已经解释的非常清楚了,不是本文的重点直接上图了
通过回弹效果理解Scroller_第1张图片
scrollTo和scrollBy的区别直接举例子,假设布局的初始位置是(0,0),我要将布局向下移动30,则应该是scrollTo(0,-30),但是这里我们使用scrollBy(0,-30)也是样的效果,因为初始位置是0,scrollBy就算是累加的也没用,加的是0,但是在当前位置,我还想要布局往下30,则可以再使用scrollBy(0,-30),但是如果还是使用scrollTo(0,-30)则不行,如果要使用scrollTo则应该是scrollTo(0,-60),这样子说应该很好理解了,因为scrollBy(x,y)是把初始位置加上x,y,而scrollTo(x,y)则是跳到x,y位置。


B. 剩下的都要通过回弹这个例子来理解了。首先对于这个回弹效果要知道可以分为两部分,第一是下拉,整个布局下移,第二是松开手,布局上移。因为整个布局是继承RelativeLayout的,对于第一部分,看到的随着手指下拉,布局下移其实是通过scrollTo使布局移动的。
对于手势的监听,这里使用class GestureListenerImpl implements GestureDetector.OnGestureListener,对于里面onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) 参数的理解很重要,
还是用这个代码测试下点击这里 代码如下

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        Log.d(TAG, "onScroll: " + e1.toString()+"    "+e2.toString()+"    distance-->"+distanceX+"  "+distanceY);
        Log.d(TAG, "e1 y--->" + e1.getRawY() + "  " + e1.getY() + "   e2 y-->" + e2.getRawY() + "  " + e2.getY());
        return true;
    }

然后只是手指在屏幕上滑动,并不快速“抛”,得到如下图结果:
通过回弹效果理解Scroller_第2张图片
因为篇幅原因,并不能很好的看清楚,我整理其中的两个好好看下,如下

MainActivity: onScroll: 
MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=632.4144, y[0]=896.533, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=97336748, downTime=97336748, deviceId=2, source=0x1002 }  
MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=629.93335, y[0]=921.4869, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=2, eventTime=97337196, downTime=97336748, deviceId=2, source=0x1002 }
distance-->2.481079  -24.953857
MainActivity: e1 y--->896.533  896.533   e2 y-->921.4869  921.4869

MainActivity: onScroll: 
MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=632.4144, y[0]=896.533, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=97336748, downTime=97336748, deviceId=2, source=0x1002 }  
MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=628.8429, y[0]=926.81665, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=2, eventTime=97337212, downTime=97336748, deviceId=2, source=0x1002 }  
distance-->1.0904541  -5.329773
MainActivity: e1 y--->896.533  896.533   e2 y-->926.81665  926.81665

对于其中的参数MotionEvent e1, MotionEvent e2,经常说e1是滑动初始位置的相关信息,e2是滑动结束位置的相关信息,这是对整个过程来说的,但是分析整个滑动过程,根据检查出来的onScroll节点,在我们手离开屏幕之前,e2并不确定,因为我们可能在其中的任何一个地方手指离开屏幕,所以才如下图
通过回弹效果理解Scroller_第3张图片
因为我监听的是整个父布局,所以getRawY()和getY()结果是一样的,在滑动过程中每GestureDetector.OnGestureListener捕捉到的e2都是变化的,再仔细看下上面列出来的两节点,distanceY = e1Y-e2Y,896.533-921.4869 = -24.953857,这就是distanceY,带符号计算出来的距离。


C. 先贴出整个代码

package guo.com.gesturepro;

/**
 * Created by ${GuoZhaoHui} on 2017/4/27.
 * email:[email protected]
 */

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.widget.RelativeLayout;
import android.widget.Scroller;

/**
 1. 原创作者:
 2. 谷哥的小弟
 3. 博客地址:
 4. http://blog.csdn.net/lfdfhl
 */
public class BounceableRelativeLayout extends RelativeLayout {
    private Scroller mScroller;
    private GestureDetector mGestureDetector;
    private final String TAG="stay4it";

    public BounceableRelativeLayout(Context context) {
        this(context, null);
    }

    public BounceableRelativeLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        setClickable(true);
        setLongClickable(true);
        mScroller = new Scroller(context);
        mGestureDetector = new GestureDetector(context, new GestureListenerImpl());
    }

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            Log.d(TAG,"-----getCurrX------       "+mScroller.getCurrX()+"-----getCurrY------      "+mScroller.getCurrY());
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();
        }
        super.computeScroll();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_UP :
                reset(0, 0);
                break;
            default:
                return mGestureDetector.onTouchEvent(event);
        }
        return super.onTouchEvent(event);
    }


    class GestureListenerImpl implements GestureDetector.OnGestureListener {
        @Override
        public boolean onDown(MotionEvent e) {
            return true;
        }

        @Override
        public void onShowPress(MotionEvent e) {

        }

        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            return false;
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY){
            int disY = (int) ((distanceY - 0.5) / 2);
            Log.d(TAG,"distanceY-->"+distanceY+"   disY-->"+disY);
            beginScroll(0, disY);
            return false;
        }

        public void onLongPress(MotionEvent e) {

        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float x,float y) {
            return false;
        }

    }


    protected void reset(int x, int y) {
        int dx = x - mScroller.getFinalX();
        int dy = y - mScroller.getFinalY();
        beginScroll(dx, dy);
    }

    protected void beginScroll(int dx, int dy) {
        Log.d(TAG,"----getFinalX1-----        "+mScroller.getFinalX()+"----getFinalY1-------       "+mScroller.getFinalY());
        mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy);
        Log.d(TAG,"----getFinalX2-----         "+mScroller.getFinalX()+"----getFinalY2-------        "+mScroller.getFinalY());
        invalidate();
    }
}

根据上面所说的onScroll就很好理解这一段代码了

   @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY){
            int disY = (int) ((distanceY - 0.5) / 2);
            Log.d(TAG,"distanceY-->"+distanceY+"   disY-->"+disY);
            beginScroll(0, disY);
            return false;
        }

中间的对于 distanceY处理也很有道理,使的手指下滑和布局下移的频率不一致,有种下拉很“困难”的感觉。
通过如下log来理解Scroller.getFinalY()和Scroller.getCurrY()的区别以及Scroller.startScroll()前后getFinalY值的变化。

 protected void beginScroll(int dx, int dy) {
        Log.d(TAG,"----getFinalX1-----        "+mScroller.getFinalX()+"----getFinalY1-------       "+mScroller.getFinalY());
        mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy);
        Log.d(TAG,"----getFinalX2-----         "+mScroller.getFinalX()+"----getFinalY2-------        "+mScroller.getFinalY());
        invalidate();
    }

通过回弹效果理解Scroller_第4张图片

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

先看看调用startScroll前后getFinalY的区别,根据源码可以知道mFinalY = startY + dy; 就比如上面的,先看第一个红色框在滑动之前最初getFinalY=0,然后加上dy=-14,所以调用startScroll方法后是getFinalY = -14,再看第二个框,当到这里时说明布局已经下移了14,这个时候的getFinalY=-14,又需要下移4(移动-4),在调用startScroll后getFinalY = -14-4=-18,后面的以此类推,一直累加。
再看getCurrY,根据上面代码可知,调用startScroll,又调用了invalidate(),在调用invalidate()后会导致View的重绘从而调用computeScroll()。如下

  @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            Log.d(TAG,"-----getCurrX------       "+mScroller.getCurrX()+"-----getCurrY------      "+mScroller.getCurrY());
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();
        }
        super.computeScroll();
    }

其实根据上面startScroll的源码可知,只要调用了这个方法都会运行这行代码
mFinished = false;,这样子在进入computeScroll这个方法时mScroller.computeScrollOffset()就会为true,才有log才会scrollTo,这一点对于理解后面手指离开屏幕那段代码非常重要。再看上面log,第一个框中的getCurrY = 0,因为确实是0,因为在得到这个结果时,并没有执行scrollTo这行代码,所以还是没有移动的。刚好这个值和startScroll之前的getFinalY是一样的,后面都符合这个规律,这也很好理解,startScroll只是赋值,也没有移动,赋值后得到getFinalY是需要移动的目标。


C. 因为前面写了

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_UP :
                reset(0, 0);
                break;
            default:
                return mGestureDetector.onTouchEvent(event);
        }
        return super.onTouchEvent(event);
    }

只要除了MotionEvent.ACTION_UP ,其他的操作比如down move,都是会重写onScroll方法的。但是当手指离开时会运行 reset(0, 0);,如下

  protected void reset(int x, int y) {
        int dx = x - mScroller.getFinalX();
        int dy = y - mScroller.getFinalY();
        beginScroll(dx, dy);
    }

这个时候得到的getFinalY是累加到手指离开屏幕最大的值(不含符号).如log图
通过回弹效果理解Scroller_第5张图片
红色框是布局移动到最后一个点,因为前面说了只要调用了startScroll这个方法,在进入computeScroll后都能经过mScroller.computeScrollOffset()的判断,所以这里还是会运行scrollTo这行代码,但是这行代码完了后,手指离开就会运行如下代码

case MotionEvent.ACTION_UP :
                reset(0, 0);
                break;

首先要注意一点,每次scrollTo后都会postInvalidate()刷新界面,既然刷新了界面,那么又将导致View的重绘,则又将调用computeScroll()方法,前面的一直下拉累加和后面情况不一样。比如以前,目标位置是 -112或者 而getCurrY=-110,经过postInvalidate();到达目标位置布局就会终止循环停止滚动,再等手指滑动。而这里看第三个框,getFinalY1-115,然后经过startScroll,则getFinalY=-115+115=0,也是对的。又是调用了startScroll,则又会进入判断,会执行scrollTo,因为这里的目标位置是getFinalY2=0,所以不像以前样,手指已经离开屏幕,不能控制,当前getCurrY=-115 目标是0,再调用postInvalidate()时一直不断循环直到位置等于0 。

3.写在最后

感谢http://blog.csdn.net/lfdfhl/article/details/53143114 ,通过它的博客学到很多知识。

你可能感兴趣的:(一切尽在掌握中)