开发中,为了增加更多炫丽的效果,我们经常在应用中添加滑动效果,今天就来分析一下 View 中滑动效果的实现原理以及几种常见的实现方式。
一、滑动原理
1. Android 中的坐标系
在 View 基础 中我们提到了 View 中的 X、Y、mLeft、mTop... 等属性,其中这些属性的值都是相对坐标系来说的,Android 中有两种坐标系,这里一一来简单说一下
Android 坐标系: 以屏幕左上角为坐标原点,向右为 X 轴正方向,向下为 Y 轴正方向,MotionEvent 的 getRawX()、getRawY() 方法获取的是点击位置在 Android 坐标系中的坐标
视图坐标系: 以当前控件左上角为坐标原点,向右为 X 轴正方向,向下为 Y 轴正方向,MotionEvent 的 getX()、getY() 方法获取的是点击位置在视图坐标系中的坐标,View 的 mLeft、mTop 等属性也是 View 在父控件的视图坐标系中的坐标
2. 滑动原理
了解了 Android 中的坐标系,再说 View 的滑动原理,其实滑动的原理与动画效果的实现非常相似,都是通过不断改变 View 的坐标来实现这一效果。所以要实现滑动效果就必须要监听用户的触摸事件,并根据事件传入的坐标,动态且不断的改变 View 的坐标,从而实现 View 跟随用户触摸的滑动而滑动
二、滑动方式
滑动过程中触摸坐标改变的监听功能我们可以通过重写 onTouchEvent() 方法实现,onTouchEvent 中根据事件类型确定当前的触摸坐标,如果如果需要实现滑动,再调用实现滑动的方法。
通过滑动原理我们知道所有修改 View 坐标的方法都可以实现滑动功能,而改标 View 坐标的方式有很多种,这里我们就介绍几种常见的滑动方式
1. 通过 layout() 方法
View 中通过滑动事件中计算滑动距离,调用 layout 方法,其原理是改变 View 的 mLeft、mTop 等坐标,将初始位置值及偏移量传入,即需要滑动到的位置的坐标,由上篇文章介绍的 layout 过程 可知,View 完成重新布局后,就达到了移动 View 的效果
2. offsetLeftAndRight() 方法和 offsetTopAndBottom()
offsetLeftAndRight(offset) offsetTopAndBottom(offset) 方法的原理是通过修改 View 的 mLeft、mTop 坐标完成滑动。
在滑动事件中,得到偏移量,调用 offsetLeftAndRight 和 offsetLeftAndRight 方法实现滑动
3. 通过修改 LayoutParams 实现滑动
通过改变 View 的 LayoutParams 参数中的 margin 值,在父 View 的布局过程中会将 View 的坐标加上相应的 margin 偏移量,从而改变 View 在父容器中的坐标,完成滑动
4.使用动画
使用 View 动画,只能改变 View 内容的位置,不能改变 View 的真正坐标
使用属性动画完成滑动,在动画执行的过程中,通过改变 View 的真正坐标实现滑动
这里总结一下,以上介绍的前三种滑动方式以及使用属性动画完成滑动,这些方式都是通过改变 View 在其父容器中的坐标从而实现的滑动,实现的是控件整体发生了滑动
5. View 的 scrollTo()、scrollBy() 方法实现滑动
scrollTo、scrollBy 实现的是 View 内容的滑动,是区别于上面提到的控件整体滑动的,其效果是 View 控件并没有滑动,而是控件上绘制的内容在控件范围内发生了滑动
在发生滑动事件时,通过调用 scrollTo 或者 scrollBy 方法完成 View 内容的滑动。
如果想要通过 scrollTo/scrollBy 方法实现 View 控件的滑动,就在需要滑动的时候,调用 view.getParent().scrollTo ,通过让其父 View 滑动其父 View 中的内容,实现该 View 的滑动效果。
并且还有一点需要注意:如果通过其父 View 调用 scrollTo/scrollBy 方法改变所以子 View 在父 View 中的位置时,并没有修改子 View 真正的坐标位置,而是修改了坐标的偏移量 translateX、translateY 、x、y 的值,其中 x = mLeft + translateX ,y 同理
scrollTo/scrllBy 方法的区别
scrollTo(int x,int y) 实现的是相对于参数的绝对滑动,即滑动结果为相对于内容的原始位置,原始位置就是 mScrollX 和 mScrollY 都是 0 的位置,滑动后 mScrollX = x ; mScrollY = y;
scrollBy(int x,int y) 中调用了 scrollTo() 方法,不过 scrollBy() 实现的是相对于当前位置的相对滑动,即相对于当前 mScrollX 和 mScrollY 的值进行的滑动,滑动结果为 mScrollX = x + mScrollX(滑动前) mScrollY = y + mScrollY(滑动前)
View 的 mScrollX 和 mScrollY 参数
mScrollX 可由 getScrollX() 方法得到,表示的是,View 左边缘跟 View 内容左边缘在水平方向的距离,并且,如果 View 左边缘在内容左边缘左侧时该值为负,View 左边缘在内容左边缘右侧时该值为正。
mScrollY 可由 getScrollY() 方法得到,表示的是,View 上边缘跟 View 内容上边缘在竖直方向的距离,并且,如果 View 上边缘在内容上边缘上侧时该值为负,View 左边缘在内容左边缘右侧时该值为正。
6. 通过 Scroller 类实现滑动
Scroller 实现滑动的原理是通过调用 scrollTo 方法实现的,所以实现的也是内容的滑动,有关内容滑动的知识请看上一节通过 scrollTo 方法实现滑动效果
实现方式:
初始化 Scroller ,调用 Scroller 的 startScroll 方法,将 X,Y 方向的初始、需要偏移值、滑动初始时间以及滑动时间传入,如果不传入滑动时间,则默认时间为 250 毫秒,接着调用 invalidate() 方法执行重绘
重写 View 的 cumputeScroll 方法,在重绘过程中调用该方法。其中通过调用 Scroller 对象的 computeScrollOffset 方法测量当前时间对应的应该发生滑动值,并将最新的需要滑动的值保存,该方法返回是否滑动完成的 boolean 值,true 滑动未完成,返回 false 表示滑动已经完成。computeScrollOffset 方法中通过时间的流逝计算当前需要滑动到的位置。
cumputeScroll 中,如果滑动未完成,通过 scroller 的 getScrollX getScrollY ,得到当前需要滑动到的位置,调用 View 的 scrollTo 方法滑动到指定位置,
再次调用 invalidate 方法,实现 View 的重绘。
再总结一下 Scroller 实现滑动的过程,invalidate 方法执行重绘,重绘过程中会调用 cumputeScroll 方法,cumouteScroll 方法中又会通过 Scroller 来计算当前需要滑动到的位置并调用滑动方法实现滑动,接着在调用 invalidate 方法重绘,从而循环绘制,直到滑动完成
7. ViewDragHelper 实现滑动
ViewDragHelper 是 Google 提供的一个类,通过 ViewDragHelper 来实现各种不同的滑动,拖放需求
- ViewGroup 中初始化,传入 ViewGroup 和 回调 CallBack ,Callback 中的方法返回值表示哪个 View 可以被移动
- 拦截事件,将 View 的触摸事件使用 ViewDragHelper 的方法拦截
- 重写 View 的 computeScroll 方法
- 在回调中重写监听方法,clampViewPositionHorizontal clampViewPositionVertical 实现横向纵向滑动
- 可以通过重写不同状态的回调实现在不同回调时的实现。
好啦,到这里有关 View 滑动的内容就介绍的差不多了,其中要区分滑动实现的是 View 控件的滑动,还是 View 内容的滑动。并且通过动画、Scroller、Handler/postDelay 等方式还可以实现弹性滑动的效果,普通滑动都是瞬时完成的,而弹性滑动则是渐进完成的,如果不太了解弹性滑动的只需要实现前面提到的几种滑动方式,一对比就明白啦。
请期待下篇文章触摸事件的分发和处理
作者:任教主来也
链接:https://www.jianshu.com/p/a177869b0382
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。