View的事件体系
在Android中任何一个与用户交互或者展示内容的控件都是由View拓展实现的。
View的基础知识
View是Android中所有控件的基类,也包括ViewGroup。ViewGroup可以理解为View的组合,内部可以包含很多View以及ViewGroup,通过这种关系就形成了一个View树。
上层的控件主要负责测量与绘制下层的控件,并传递交互事件。
1. View的位置参数
1.Android坐标系
在Android中,将屏幕左上角的顶点作为坐标原点,向右为X轴增大方向,向下为Y轴增大方向
2.View坐标系
View的位置由它的四个顶点决定,分别对应View的4个属性:
left(左上角横坐标)、top(左上角纵坐标)、right(右下角横坐标),bottom(右下角纵坐标)
。这些坐标都是相对于View的父容器决定的。
1 |
|
在Android3.0之后添加了几个新参数x,y,translationX,translationY
。
1 |
|
left是View的初始坐标,不会改变的;x是View偏移后的坐标,偏移后就会发生变化
Android系统也提供了相应的方法可以直接获取对应参数。但是,不能在初始时就去获取,由于那时View还没有开始绘制,获取的都是0;
如何获取可以参考这个 Android Study Plan V
2.View触控
1.MotionEvent
MotionEvent
:提供点击事件的事件行为以及事件发生的x,y坐标,典型事件由:
- ACTION_DOWN:监听用户手指按下的操作,一次按下标志触摸事件的开始。
- ACTION_MOVE:用户按压屏幕后,在抬起之前,如果移动的距离超过一定数值,就判定为移动事件。
- ACTION_UP:监听用户手指离开屏幕的操作,一次抬起标志触摸事件的结束。
- ACTION_CANCEL:当用户保持按下操作,并把手指移动到了控件外部局域时且父View处理事件触发。
事件列:从手指接触屏幕到离开屏幕,产生的一系列事件。
任何事件列都是从ACTION_DOWN开始到ACTION_UP结束,中间会穿插着ACTION_MOVE事件
1 |
|
2.TouchSlop
TouchSlop
:系统所能识别的被人误是滑动的最小距离。当手指在屏幕上滑动时,如果滑动的距离小于这个值,就不会认为在进行滑动操作。
利用ViewConfiguration.get(getContext()).getScaledTouchSlop()
获取该常亮
3.VelocityTracker
VelocityTracker
:速度追踪,用于追踪在手指滑动过程中的速度,包括水平和垂直方向的速度
样例演示:
1 |
|
4.GestureDetector
GestureDetector
:手势检测,用于辅助检测用户的单击、滑动、长按,双击等行为。
样例演示:
1 |
|
View的滑动
Android由于手机屏幕比较小,为了呈现更多的内容就需要滑动来展示。
1.使用scrollTo()/scrollBy()
scrollTo()
以及scrollBy()
是由View本身提供的滑动方法。只对View的内容进行滑动,而不能使View本身滑动。
1 |
|
scrollTo(x,y)
表示移动到一个具体的坐标点 绝对滑动
scrollBy(x,y)
表示移动的增量为x,y,即在原有位置上移动x,y的距离 相对滑动
mScrollX和mScrollY分别表示View在X,Y方向的滚动距离。
mScrollX
:View的左边缘减去View的内容的左边缘 从右向左为正,反之为负
mScrollY
:View的上边缘减去View的内容的上边缘 从下向上为正,反之为负
2.使用动画
通过动画给View增加平移效果。通过改变View自身的
translationX
和translationY
属性达到滑动效果。
普通动画:新建translate.xml
动画文件,定义好tranlate属性即可实现滑动动画。
普通动画并不能修改View的位置参数,只是执行了一个动画,实际位置还是初始地方。
属性动画:ObjectAnimator.ofFloat(view,"translationX",0,300).setDuration(1000).start();
即可实现动画
属性动画真正对View的位置参数进行修改,所以对应时间都是跟随的。
3.改变布局参数
改变View的
LayoutParams
使得View重新布局。
滑动时,可以对LaqyoutParams
的margin
相关参数进行加减就可以实现滑动。
4.*弹性滑动
上述提到的方案除了动画,滑动都是很生硬的,就是闪烁过去的。所以需要实现弹性滑动(渐进式滑动)。
1. *Scroller
使用实例:
1 |
|
工作原理:
构造Scroller
对象时,内部没有做什么,只是保存了我们传递的参数
1 |
|
保存完参数后,就需要调用Scroller
的startScroll()
方法,传入对应参数进行滑动
1 |
|
调用startScroll()
后,我们调用了invalidate()
导致View进行了重绘,重绘过程中调用了draw()
方法,draw()
中调用了对应的computeScroll()
方法。computeScroll()
中又调用了Scroller
的computeScrollOffset()
方法,使Scroller
对应的mCurrX以及mCurrY
发生变化,配合View自身的scrollTo()
产生滑动事件。后续继续调用了postInvalidate()
使View重绘,按照上述流程继续执行,直到动画完成为止。
关键方法为startScroll()
及computeScroll()
总结一下原理:Scroller并不能使View进行滑动,他需要配合View的computeScroll()方法才能完成滑动效果。在computeScroll()中不断让View进行重绘,每次重绘需要计算滑动持续的时间,根据这个时间计算出应该滑动到的位置,然后调用了View本身的scrollTo()配合位置进行滑动,多次的短距离滑动形成了弹性滑动的效果。
2. 动画
3. 延时策略
通过发生一系列延时消息而达到一种渐进式的效果,具体可以使用
Handler,View.postDelayed()或者Thread.sleep()
实现
如果要求精确的话,不建议使用延时策略实现。
View的事件分发机制
{%post_link Android事件分发%}View的滑动冲突
滑动冲突场景
外部滑动和内部滑动方向不一致
外层ViewGroup是可以横向滑动的,内层View是可以竖向滑动的。例如:ViewPager嵌套ListView
外部滑动和内部滑动方向一致
外层ViewGroup是可以竖向滑动的,内层View是也可以竖向滑动的。例如:ScrollView嵌套ListView
两种情况的嵌套
滑动冲突处理规则
内外滑动方向不一致 处理规则
根据滑动是水平滑动还是竖直滑动来判断由谁来拦截事件。可以得到滑动过程中两个点的坐标,依据滑动路径与水平方向形成的夹角(斜率
)判断,或者水平和竖直方向滑动的距离差进行判断。在ViewPager中当斜率小于0.5时判断为水平滑动。
内外滑动方向一致 处理规则
一般从业务上找突破点。根据业务需求,规定何时让外部View拦截事件何时由内部View拦截事件。
嵌套滑动 处理规则
滑动规则更复杂,所以还是要从业务代码上下手。
滑动冲突解决方案
外部拦截法
点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,不需要就放行
需要重写父容器的onInterceptTouchEvent()
,在方法内部做相应的拦截。
1 |
|
是否拦截需要在ACTION_MOVE
中进行判断,父容器需要拦截事件返回true,反之返回false。
内部拦截法
父容器不拦截任何事件,所有事件交由子容器进行处理,如果子容器需要就消耗事件,不需要就返给父容器处理。
需要同时重写父容器的onInterceptTouchEvent()
以及子容器的dispatchTouchEvent()
。需要配合requestDisallowInterceptTouchEvent
1 |
|
两种方法相比较而言,外部拦截法
相比内部拦截法
实现起来更加简单,而且符合View的事件分发,推荐使用外部拦截法
。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!