前置知识
在处理 View 滑动前,需要对 View 的手势事件的传递、拦截、分类有一个全面的了解。
View 的滑动可以分为两部分,一部分是对 View 触摸时间的处理,如:手指按下 DOWN 、滑动的 MOVE,另一部分结合手势来处理 View 滑动。
滑动手势分类
区别与手势事件的类别,从手势事件序列是否正常的角度出发(既事件序列从 down 开始到up / cancel 结束有始有终为正常的一个时间序列),对于滑动手势的处理分为两种:
- 正常处理,从 DOWN 开始。
- ViewGroup 拦截子view 事件,从 MOVE 开始处理。
两种滑动手势的处理的差别只在手指初始点位置的获取上,正常的处理从 DOWN 事件触发处获取初始点即可,但如果是 ViewGroup 拦截了子 View 的事件,则需要重拦截点处获取初始点位置。
使 View 滑动
View 的视图大小是有限制的,但是内容是无大小限制的,因此View 本身是支持滑动的,提供了两个 api 来支持 scrollTo() / scrollBy() 。前者是绝对的,后者是相对当前的位置进行滑动。
来来来,处理滑动
得知了使 View 滑动的方法了,现在来处理一下 View 的滑动。
-
在 View 时间 DOWN 或者拦截事件时记录下当前手指位置 - lastPoint。
-
在滑动事件 MOVE 中获取当前滑动手指的位置,与 lastPoint 进行计算,得出手指滑动的距离。
-
有了滑动的距离之后有了滑动距离只有就可以调用 scrollXXX 进行滑动了,当然,需要做好是否可以滑动的判断。
-
这样滑动处理流程就就到此为此了吗?并没有。如果手指滑动的慢的这样处理是可以的,「跟手感」是符合人的认知的,但是在手指离开屏幕时滑动速度很快的情况下,直接停下不动,这样就脱离了人的物理感受,体验很差。需要让 View 有一个惯性滑动,手指速度越快,惯性滑动越快,这样才符合人类的现实体验。 基于上述分析,想要完成惯性滑动要破解两个关键的地方:
- 怎样获取速度?
- 怎样根据获取的速度滑多远、滑多久?
搞定惯性滑动 Fling
获取速度
速度怎么计算?是距离÷时间吗?按照这个公式计算肯定是正确的,但是系统已经给我们提供了计算速度的工具类:VelocityTracker,只需要几步便可获取速度。
- 每次事件序列,使用一个新的
VelocityTracker实例,使用其obtin()函数实例化。 - 每次 MOVE 事件触发时使用
addMovement()函数进行追踪。 - 在手指抬起来或收到 CANCEL 事件时,使用
computeCurrentVelocity()进行计算。 - 使用 getYVelocity() 获取 Y/X 方向的速度。
- 完成一次滑动事件序列处理,回收处理
VelocityTracker实例。
执行惯性滑动
得到了速度,又怎样根据这个速度滑多远、滑多久呢?这是一个复杂的算法,幸好系统也是提供了工具类:Scroller帮助我们来处理惯性滑动。
Scroller本身并不能滑动,而是需要我们不断的调用其 computeScrollOffset() 函数来计算每次该滑动 x / y 的值,然后再不断的获取其所计算的 scrollX / scrollY 值进行 View.scrollTo() 调用来完成平滑的惯性滑动,这其中还需要 View 的重绘机制来进行配合,View 每次进行 draw() 时都会调用computeScroll() 函数来进行滑动。因此,Scroller整个惯性滑动过程处理如下:
- 初始化 Scroller ,在需要进行惯性滑动时条勇气 fling() 函数,给入需要的参数。
- 紧接着fling() 函数调用 invalidate() 函数,使用进行重绘。
- View 重绘会调用 computeScroll() 函数,在computeScroll() 函数中调用 Scroller.computeScrollOffset() 计算当前滑动值,并获取当前滑动的 x / y 值,调用 scrollTo() 函数。
- scrollTo() 函数会导致 View 重绘,进而重复该过程,直至Scroller.computeScrollOffset() 返回 false 为止。
小结
其实 View 的滑动处理是一套定式,没有多少自由发挥的余地,掌握了之后处理滑动就是一套模板代码。当我们对 View 滑动处理有疑问时,参考现有的系统 View 如:ScrollView 能起到一个很好的指导效果。
\