通过回弹效果理解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 ,通过它的博客学到很多知识。相关文章推荐
- android 实现拉出回弹效果通过自定义ListView重写overScrollBy()
- android 通过Scroller实现过渡滑动效果
- Cocos2d-x--全民飞机大战弹窗回弹效果
- 通过隐藏option实现select的联动效果
- ASP.NET jQuery 实例9 通过控件hyperlink实现返回顶部效果
- 通过qss实现Qt按钮的默认效果——qconicalgradient
- 简单的回弹效果
- 通过下拉来学习Scroller的内容滑动原理
- 今天帮美工写个很简单的javascript菜单效果,在ie6, firefox下测试通过,整理了下代码,贴出来。
- 通过分频器和乘法器来实现对频率的减倍和倍增效果
- 三种布局实现上下回弹效果(普通布局,ListView,ScrollView)
- 通过自定义View实现虚线效果
- Web_CSS_通过不断设置广告位置来实现广告停留视野效果;
- 在安卓中,怎么通过ViewPager来实现轮播图的效果?
- 通过python threading Thread理解多线程和单线程的运行机制
- 简单入门canvas - 通过刮奖效果来学习
- 通过简单的实例对指针的理解
- 自定义ScrollView实现放大回弹效果
- CoordinatorLayout初体验以及标题栏下方图片的回弹效果
- 通过Launcher里的WorkSpace完成桌面的3D转屏效果