【Android事件分发机制理解】春衣少年当酒歌,起舞四顾以笑和。这篇文章主要讲述Android事件分发机制理解相关的知识,希望能为你提供帮助。
预备知识
触摸事件 :
- 安卓中把触摸事件封装成了一个类MotionEvent,用户的一次点击、触摸或者滑动都会产生一系列的MotionEvent
- 这个类的内容很简单,就两个东西:事件类型+坐标xy
- 事件类型有四种
- MotionEvent.ACTION_DOWN 表示用户的手指刚接触到屏幕
- MotionEvent.ACTION_MOVE 表示用户的手指正在移动
- MotionEvent.ACTION_UP 表示用户的手指从屏幕上抬起
- Cancel
- 所以一次用户触摸屏幕可能会产生这些事件:
- 点击屏幕然后松开,Down-> Up
- 点击屏幕,然后滑动一段距离,松开屏幕 ,Down-> Move-> … -> Move-> Up
- dispatchTouchEvent(MotionEvent event)
- onInterceptTouchEvent(MotionEvent event)
- onTouchEvent(MotionEvent event)
- 只考虑最重要的四个触摸事件,即:DOWN,MOVE,UP和CANCEL
- 一个手势(gesture)是一个事件列
- 以一个DOWN事件开始(当用户触摸屏幕时产生)
- 后跟0个或多个MOVE事件(当用户四处移动手指时产生)
- 最后跟一个单独的UP或CANCEL事件(当用户手指离开屏幕或者系统告诉你手势(gesture)由于其他原因结束时产生)
- 当我们说到“ 手势剩余部分” 时指的是手势后续的MOVE事件和最后的UP或CANCEL事件
- 不考虑多点触摸手势(我们只假设用一个手指)
- 忽略多个MOVE事件可以被归为一组这一实际情况
- 假设文中的view都没有注册onTouchListener
- 假设一种视图层次:
- 最外层是一个ViewGroup A,包含一个或多个子view(children),其中一个子view是ViewGroup B,ViewGroupB中又包含一个或多个子view,其中一个子view是 View C,C不是一个ViewGroup
- 忽略同层级view之间可能的交叉叠加
- 假设情况:
- 用户首先触摸到的屏幕上的点是C上的某个点,该点被标记为触摸点(touch point),DOWN事件就在该点产生
- 然后用户移动手指并最后离开屏幕,此过程中手指是否离开C的区域无关紧要,关键是手势(gesture)是从哪里开始的
- 假设不考虑dispatchTouchEvent方法
- DOWN事件被传到C的onTouchEvent方法中,该方法返回false,表示“ 我不关心这个手势(gesture)”
- 因此,DOWN事件被传到B的onTouchEvent方法中,该方法同样返回false,表示B也不关心这个手势
- 同样,因为B不关心这个手势,DOWN事件被传到A的onTouchEvent方法中,该方法也返回false
- 由于没有view关心这个手势(gesture),它们将不再会从“ 手势剩余部分” 中接收任何事件
- DOWN事件被传递给C的onTouchEvent方法,该方法可以做任何它想做的事情,最后返回true。
- 因为C说它正在处理这个手势(gesture),则DOWN事件将不再被传递给B和A的onTouchEvent方法。
- 因为C说它正在处理这个手势(gesture),所以“ 手势剩余部分” 的事件也将传递给C的onTouchEvent方法,此时该方法返回true或false都无关紧要了,但是为保持一致最好还是返回true。
- onInterceptTouchEvent方法它只存在于ViewGroup中,普通的View中没有这个方法
- 在任何一个view的onTouchEvent被调用之前,它的父辈们将先获得拦截这个事件的一次机会
- 换句话说,它们可以窃取该事件。在刚才的“ 处理事件” 部分中,我们遗漏了这一过程
- DOWN事件被传给A的onInterceptTouchEvent,该方法返回false,表示它不想拦截。
- DOWN又被传递给B的onInterceptTouchEvent,它也不想拦截,因此该方法也返回false。
- 现在,DOWN事件被传递到C的onTouchEvent方法,该方法返回true,因为它想处理以该事件为首的手势(gesture)。
- 现在,该手势的下一个事件MOVE到来了。这个MOVE事件再一次被传递给A的onInterceptTouchEvent方法,该方法再一次返回false,B也同样如此。
- 然后,MOVE事件被传递给C的onTouchEvent,就像在前一部分中一样。
- “ 手势剩余部分” 中其他事件的处理过程和上面一样,假如A和B的onInterceptTouchEvent方法继续返回false的话
- 虽然ViewGroup A和B的onInterceptTouchEvent方法对DOWN事件返回了false,后续的事件依然会传递给它们的onInterceptTouchEvent方法,这一点与onTouchEvent的行为是不一样的
- 假如DOWN事件传给C的onTouchEvent方法时,它返回了false,DOWN事件会继续向上传递给B和A的onTouchEvent,即使它们在onInterceptTouchEvent方法中说它们不想拦截这个DOWN事件,两者是独立的
- DOWN事件被依次传到A和B的onInterceptTouchEvent方法中,它们都返回的false,因为它们目前还不想拦截。
- DOWN事件传递到C的onTouchEvent方法,返回了true。
- 在后续到来MOVE事件时,A的onInterceptTouchEvent方法仍然返回false。
- B的onInterceptTouchEvent方法收到了该MOVE事件,此时B注意到用户手指移动距离已经超过了一定的threshold(或者称为slop)。因此,B的onInterceptTouchEvent方法决定返回true,从而接管该手势(gesture)后续的处理。
- 然后,这个MOVE事件将会被系统变成一个CANCEL事件,这个CANCEL事件将会传递给C的onTouchEvent方法。
- 现在,又来了一个MOVE事件,它被传递给A的onInterceptTouchEvent方法,A还是不关心该事件,因此onInterceptTouchEvent方法继续返回false。
- 此时,该MOVE事件将不会再传递给B的onInterceptTouchEvent方法,该方法一旦返回一次true,就再也不会被调用了。事实上,该MOVE以及“ 手势剩余部分” 都将传递给B的onTouchEvent方法(于是根本不会传给子view)(除非A决定拦截“ 手势剩余部分” )。
- C再也不会收到该手势(gesture)产生的任何事件了
- 如果一个ViewGroup拦截了最初的DOWN事件,该事件仍然会传递到该ViewGroup的onTouchEvent方法中。
- 另一方面,如果ViewGroup拦截了一个半路的事件(比如,MOVE),这个事件将会被系统变成一个CANCEL事件,并传递给之前处理该手势(gesture)的子View,而且不会再传递(无论是被拦截的MOVE还是系统生成的CANCEL)给ViewGroup的onTouchEvent方法。只有再到来的事件才会传递到ViewGroup的onTouchEvent方法中。
- 比如对覆写 requestDisallowInterceptTouchEvent,C可以用该方法阻止B窃取事件
- 如果你想更加疯狂一点,你可以在你自己的ViewGroup中直接覆写dispatchTouchEvent方法,并对传递进来的事件做任何你想做的处理
- ViewGroupA首先收到事件,说一声,我收到了
- 但是他先不处理,它先转发给子View,然后说,我转发了
- ViewGroupB然后收到事件,也说一声,我收到了
- 但是他也先不处理,它先转发给子View,然后说,我转发了
- ViewC最后收到事件,那也说一声,我收到了
- (--------上面是事件传递过程,下面是事件处理过程--------)
- 他直接尝试处理,说,我尝试处理一下
- (结果发现自己无法处理,没有消耗事件,于是事件原路返回)
- ViewGroupB收到返回的事件,那就处理一下呗,于是说,我尝试处理一下
- ViewGroupB也收到返回的事件,也说,我尝试处理一下
- 我收到了:dispatchTouchEvent [派遣,发送]
- 我转发了:onInterceptTouchEvent [拦截]
- 我尝试处理一下:onTouchEvent
- 前两句话是事件传递过程的
- 返回true,表示拦截,到此中断,就不往下走了;
- 返回false,表示不拦截,继续往下走
- 第三句话是事件处理过程的(完全一模一样,只是意义不同)
- 返回true,表示拦截,到此中断,就不往下走了;
- 返回false,表示不拦截,继续往下走
- 默认情况下,三个方法都是返回false
- 而每个地方其实都可以改成true,这样,回路就直接中断了
- 但是如果在onInterceptTouchEvent这个方法中返回true,那么不是直接结束回路,而是会跳到对应的onTouchEvent回路也就是事件处理回路中,并且所有外层的onTouchEvent都会被执行
- 在 onTouchEvent方法中返回true,就确实是直接中断
- 这个View是什么;两种情况:View、ViewGroup;会影响回调方法数
- 这个方法是什么;三种情况:;两类情况:事件传递过程、事件处理过程;会影响后续怎么跳
- 这个方法返回什么;两种情况:true、false;会影响是否结束回路
- 这个事件的类型;四种情况:DOWN,MOVE,UP和CANCEL ;
public boolean dispatchTouchEvent(MotionEvent event) { boolean consume = false; if (onInterceptTouchEvent(event)) { consume = onTouchEvent(event); } else { consume = child.dispatchTouchEvent(event); } return consume; } |
- 在dispatchTouchEvent中,先调用ViewGroup自身的onInterceptTouchEvent方法,判断自己是否要拦截
- 如果这时候自己拦截,那就调用自己的onTouchEvent方法
- 如果onTouchEvent方法返回了True,那么这次的事件就算消耗了,事件传递到此为止
- 如果返回了False,证明这次没有消耗这次MotionEvent,那么这次的事件就会往上返回,由上一级继续处理;
- 如果当前ViewGroup的onInterceptTouchEvent返回了False,那就会调用它的子view的dispatchTouchEvent方法,这样这个事件就传递下去了
- 如果这时候自己拦截,那就调用自己的onTouchEvent方法
- 如果它的子View处理不了,那么还会回来调用ViewGroup的onTouchEvent方法,当然这一点是没有在这一段伪代码里体现的
- 一个完成的事件序列以Down开始,中间可能包含若干个Move,然后以Up结束
- 一个view一旦拦截某个事件,当前事件所在的完整事件序列将都会由这个view去处理
- 反应在真实的代码中,就是一旦view拦截了down事件,那么此后的move和up事件都将不调用onInterceptTouchEvent,而直接由它处理
- 这就也意味着在onInterceptTouchEvent处理事件是不合适的,因为有可能来了事件,却直接跳过onInterceptTouchEvent方法
- 这个也意味着,一旦一个ViewGroup没有拦截ACTION_DOWN,那么这个事件序列的其他Action,它都将收不到,所以在处理ACTION_DOWN的时候,尤其需要谨慎
- ( 哦,所以这就很清晰了,dispatch只是入口,它什么都不干,然后intercept是一个第一次拦截的标志,只会被调用一次,最后ontouch才是真正处理事情,根据不同的event做不同的处理 )
- onTouchEvent中是要判断MotionEvent的Action,因为一次点击操作就会调用两次onTouchEvent方法,一次是ACTION_DOWN,一次是ACTION_UP,如果手滑一下,还会有若干个ACTION_MOVE
- ViewGroup默认不拦截任何事件,源码中ViewGroup的onInterceptTouchEvent方法默认返回的是false
- 整个事件分发,看起来都是由外向内传递的,父View将事件传递给子View,理论上来看,子View是没有办法影响到父View的事件处理的,但是有一个标示位,requestDisallowInterceptTouchEvent方法,通过这个方法 ,子View能够影响父view的事件处理,这个可以用于解决父view和子view的滑动冲突
推荐阅读
- No.2 IonicAndroid打包
- Android 手机卫士--自定义属性
- Android Multimedia框架总结(十四)Camera框架初识及自定义相机案例
- 程序员带你学习安卓开发系列-Android文件存储
- Android独立音量播放器
- android MVP模式初认识-1
- android studio出现Error:compileSdkVersion android-x requires compiling with JDK 7问题
- Android Studio 快捷键一览
- Android数据库的运用