又有一段时间没有更新博客了,每次简书上的朋友给我之前的文章点赞或者评论,手机的推送都会响起,心中总有种说不出的感觉,挺难受的。发现真正投入工作后,没有像在学校里有那么一整块完整的时间可以让自己去自我填充,整理一些知识点。
今天打算来讲下关于Android的事件分发机制,Android的事件分发在平时工作中零零散散的也用到许多,特别是在自定义View和多层嵌套布局的情况下。
要了解Android的事件分发机制,首先我们要从最常见的一个用户行为说起——触摸屏幕,当我们触摸手机屏幕时发生了什么?
这里通常会产生2-3个事件:
事件一:按下
事件二:滑动(如果在点击屏幕的时候,用户手抖了一下)
事件三:抬起
Android为这三个事件(触摸事件)封装了一个完整的类——MotionEvent,当你对任意的View进行onTouchEvent方法重写的时候,就可以发现方法参数里有个MotionEvent类,在MotionEvent类中我们可以得到许多东西,比如获取当前触碰点的x,y坐标,获取当前触碰的动作action(按下,滑动,抬起),我们可以根据这些数据的组合来确定用户的手指行为,从而可以实现较为复杂的逻辑动作。
那么问题来了,在一个嵌套界面中,比如一个ViewGroup里嵌套了另一个ViewGroup,在另一个ViewGroup里再嵌套一个View,当我触摸他们重叠的地方时,系统是如果识别当前的触摸位置的?
如下图:
这里的蓝色和红色分别代表ViewGroupA和ViewGroupB,这里的绿色代表View,它们存在一个相对布局里,一个叠着一个,当我们触摸绿色区域的时候,发生了什么事情?
**这里需要引出3个方法: **
1、dispatchTouchEvent(ViewGroup和View均有):用来进行事件的分发,如果触摸事件可以传递给当前的View,那么这个方法一定会被调用。
2、onInterceptTouchEvent(View没有):在dispatchTouchEvent方法的内部调用,用于判断该事件是否被拦截。
3、onTouchEvent(ViewGroup和View均有):在dispatchTouchEvent方法的内部调用,用于判断该事件是否被消费。
现在我们先来理一下这三者之间的关系:
我们把ViewGroupA比作老板,ViewGroupB比作经理,View比作员工。
当有任务的时候,通常是层级指派关系(老板-->经理-->员工),当完成任务的时候,任务的提交也是层级传递(员工-->经理-->老板)。
老板,经理,员工都是独立的个体,他们都有自己对应的权限,就当前这个任务而言,老板如果觉得简单,他可以选择自己解决掉,但是如果老板觉得太困难了,他可以把任务下发给经理解决,此时经理也是有对应的权限,他可以选择自己解决,也可以选择将任务下发,给下一级的员工解决,那员工呢?当然就是最苦逼的那个了,因为他最小,没有继续下发指派的人了,他只能自己干了。
这里引出了一个知识点:
View(员工)比ViewGroup(老板,经理)少了一层拦截权限,也就是拦截方法onInterceptTouchEvent,这一点结论我们可以在源码中得到。聪明的你,应该可以感觉到,任务是否往下指派就取决于onInterceptTouchEvent这个方法,顾名思义“是否拦截”。而任务的处理在于各自的onTouchEvent方法里。
这里需要知道两点:
1、事件的传递(上往下):onInterceptTouchEvent拦截方法有个布尔类型的返回值,这里可以理解成是否拦截,如果为true,拦截,如果false,不拦截。
2、事件的消费(下往上):onTouchEvent事件处理方法里也有个布尔值类型的返回值,如果当前事件被处理(消费)了,那么他返回true,如果没有被消费需要传递给下一级处理,那么它返回false。
我们再来完整分析下任务与人物之间的关系,当一个任务下发的时候:
完全不阻拦的方式:老板拿到任务分发给经理,经理拿到任务分发给员工,员工处理。员工处理完任务提交给经理,经理审核完任务,提交给老板。
阻拦方式:老板拿到任务可以选择处理或者不处理,如果老板选择了把任务拦截下来自己处理了,也就是在ViewGroupA中的onInterceptTouchEvent返回了true(拦截),然后在自身的onTouchEvent里处理事件,反之下发任务给经理,经理同上,最后如果员工拿到了任务,就不会有是否拦截的说法,它只能在onTouchEvent直接处理任务。
接下来我们来结合代码验证下我们刚才说的场景吧,3个类,ViewGroupA,ViewGroupB,View,分别重写dispatchTouchEvent,onInterceptTouchEvent(View除外),onTouchEvent,然后在方法里打印Log日志,布局为最上面那张三色图。
现在我们点击下View区域,看下控制台显示的情况:
哈哈,和我们最初预想的结果是一样的,老板(ViewGroupA)接收到任务(dispatchTouchEvent),然后执行了是否拦截的方法(onInterceptTouchEvent),不拦截把任务传递给经理(ViewGroupB),然后依旧一样执行了是否拦截的方法(onInterceptTouchEvent),经理不拦截再将任务交给员工(View)处理,员工接收任务没办法分配,只能自己处理(onTouchEvent)了,处理完毕上报经理,经理再上报老板。
当然员工也有不乐意的一天,天天累死累活的,今天劳资不干了,在处理onTouchEvent返回true,看下会发生什么情况。
结果也和我们预想的一样,员工在onTouchEvent方法里返回了true,消费了该事件,也就没有事件再往上传递了。
由此可推,如果员工在onTouchEvent方法中返回的是false,那么事件就会继续向上传递,此时ViewGroupB的onTouchEvent方法中返回true,那么就只会到这层,不会再传递到ViewGroupA的onTouchEvent中去。
这里总结下几个结论:
1、一个触摸事件通常是由手指按下的那一刻起,中间经历了n个移动,最后以手指的上抬动作为止。
2、一个触摸事件通常只能被一个View所拦截(onInterceptTouchEvent)且被消耗(onTouchEvent),因为一旦onTouchEvent返回true,事件就不再传递了。
3、ViewGroup默认不拦截任何事件,这点我们可以在源码中ViewGroup的onInterceptTouchEvent方法里得知,因为他默认返回false。
4、View没有onInterceptTouchEvent方法,事件一旦传递给它,它的onTouchEvent方法就会被调用。
5、View默认的onTouchEvent都会被消耗,默认返回true,除了本身不可点击的View之外,比如TextView,ImageView等。
6、View的enable属性不影响onTouchEvent默认的返回值,就算它是disable状态,只要他的clickable或者longClickable有一个为true,那么它的返回值默认就为true。
7、事件的传递是由外到内的,事件总是先传递给父元素再传递给子元素,可以通过requestDisallowInterceptTouchEvent方法在子元素中干预父元素的事件分发,按下事件除外。
8、当ViewGroup的onInterceptTouch返回为true的时候,那么在这个事件序列中剩下的触摸事件都会由这个ViewGroup的onTouchEvent来处理,就不会再向下传递给ViewGroup或者View了,如果onInterceptTouch返回为false,则接下去的触摸事件会继续向下传递。
9、如果对View或者ViewGroup设置了OnTouchListener监听,则会重写onTouch方法,这里的onTouch优先级会高于onTouchEvent,如果onTouch返回为true,那么事件就会被消费,不会再传递给onTouchEvent了。
好了,今天先写到这吧,了解上面所说的知识点,应付日常工作所遇到的一些问题基本够用了。