您的位置:首页 > 其它

通过回弹效果理解Scroller

2017-04-27 22:48 225 查看

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里面的内容,但是使用时内容总是会往相反的方向跑,这里那位大佬已经解释的非常清楚了,不是本文的重点直接上图了



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


然后只是手指在屏幕上滑动,并不快速“抛”,得到如下图结果:



因为篇幅原因,并不能很好的看清楚,我整理其中的两个好好看下,如下

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并不确定,因为我们可能在其中的任何一个地方手指离开屏幕,所以才如下图



因为我监听的是整个父布局,所以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:guozhaohui628@gmail.com
*/

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




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图



红色框是布局移动到最后一个点,因为前面说了只要调用了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 ,通过它的博客学到很多知识。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: