android事件分发touchevent的dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent理解
参考:https://blog.****.net/morgan_xww/article/details/9372285
https://www.jianshu.com/p/35a8309b9597
基础知识
布局可定义应用中的界面结构(例如 Activity 的界面结构)。布局中的所有元素均使用 View 和 ViewGroup 对象的层次结构进行构建。View 通常绘制用户可查看并进行交互的内容。然而,ViewGroup 是不可见容器,用于定义 View 和其他 ViewGroup 对象的布局结构,如图 1 所示。
View 对象通常称为“微件”,可以是众多子类之一,例如 Button 或 TextView。
ViewGroup 对象通常称为“布局”,可以是提供其他布局结构的众多类型之一,例如 LinearLayout 或 ConstraintLayout。
touch事件3个方法:
public boolean dispatchTouchEvent(MotionEvent ev); //用来分派event;从ViewGroup往view派发
public boolean onInterceptTouchEvent(MotionEvent ev); //用来拦截event;返回true表示拦截
public boolean onTouchEvent(MotionEvent ev); //用来处理event;处理触控
Activity类 | Activity | dispatchTouchEvent(); onTouchEvent(); |
ViewGroup子类 | FrameLayout、LinearLayout、ListView等 | dispatchTouchEvent(); onInterceptTouchEvent(); onTouchEvent(); |
View控件 | Button、TextView、EditText…… | dispatchTouchEvent(); onTouchEvent(); |
dispatchTouchEvent() | 用来分派事件。 其中调用了onInterceptTouchEvent()和onTouchEvent(),一般不重写该方法 |
|
onInterceptTouchEvent() | 用来拦截事件。 ViewGroup类中的源码实现就是{return false;}表示不拦截该事件, 事件将向下传递(传递给其子View); 若手动重写该方法,使其返回true则表示拦截,事件将终止向下传递, 事件由当前ViewGroup类来处理,就是调用该类的onTouchEvent()方法 |
|
onTouchEvent() |
|
此处参考:https://blog.****.net/morgan_xww/article/details/9372285
重点掌握
touch消息种类(基础版)
ACTION_DOWN->按下
ACTION_MOVE->移动
ACTION_UP->松开
dispatchTouchEvent-事件分发顺序
dispatchTouchEvent比较复杂,可以按照下面这张图分析:ViewGroup和View组成了一棵树形结构,最顶层为Activity的ViewGroup,下面有若干的ViewGroup节点,每个节点之下又有若干的ViewGroup节点或者View节点,依次类推。
当一个Touch事件(ACTION_DOWN)依次下发,下发的过程是调用子View(ViewGroup)的dispatchTouchEvent方法实现的。简单来说,就是ViewGroup遍历它包含着的子View,调用每个View的dispatchTouchEvent方法,而当子View为ViewGroup时,又会通过调用ViewGroup的dispatchTouchEvent方法继续调用其内部的View的dispatchTouchEvent方法。上述例子中的消息action_down下发顺序是这样的:①-②-⑤-⑥-⑦-③-④。
dispatchTouchEvent方法只负责事件的分发,它拥有boolean类型的返回值,当返回为true时,(ACTION_MOVE与ACTION_UP)顺序下发会中断。在上述例子中如果⑤的dispatchTouchEvent返回结果为true,那么⑥-⑦-③-④将都接收不到本次Touch事件但只收到了(ACTION_DOWN)。
ViewGroup的dispatchTouchEvent是真正在执行“分发”工作,而View的dispatchTouchEvent方法,并不执行分发工作,或者说它分发的对象就是自己,决定是否把touch事件交给自己处理,而处理的方法,便是onTouchEvent事件。
onTouchEvent-事件处理
- 如果返回值是true,表示消费(consume)了这个事件。以ACTION_DOWN为例,如果某个控件的onTouchEvent返回值为true,则后续的n个ACTION_MOVE与1个ACTION_UP都会逐层(通过ViewGroup,不会再遍历View)传递到这个控件的onTouchEvent进行处理。
- 如果返回值是false,则会将ACTION_DOWN传递给其父ViewGroup的onTouchEvent进行处理,直到由哪一层ViewGroup消费了ACTION_DOWN事件为止。
- 由于触摸事件都是连续的。如果ACTION_MOVE传递到子控件,而子控件的onTouchEvent返回值是false,即没有处理该ACTION_MOVE事件,则后续的ACTION_UP就不会传到该子控件来了。
- 这里要注意是逐层,也就是说每层(指的只是ViewGroup)的拦截器还是可以拦截到后续的ACTION_MOVE与ACTION_UP。如果后续的ACTION_MOVE与ACTION_UP被某层的拦截器拦截,则后续的事件将不会再传递给之前处理onTouchEvent的子控件,而是逐层传递给由拦截消息的这个控件的onTouchEvent函数进行处理,并且会向其之前接收事件的子控件发送一个ACTION_CANCEL,表示后续事件被取消了。
参考:https://www.jianshu.com/p/35a8309b9597
举例子
布局结构和ontouch事件的传递顺序
长这样:
注:XML的绘制顺序是从外层到内层,同一层从上到下,绘制迟的覆盖绘制早的;在这里的XML,无论是否把MyButton写在TextView后面,由于MyButton显示优先级高于TextView所以显示在TextView上,并且获取事件优先级也高于TextView。
1和2点的逻辑示意图
1.点白色部分FrameLayout:Frame的OntouchEvent返回false表示不消费事件;ACTION_UP就不传给他了
引用图片:https://blog.****.net/morgan_xww/article/details/9372285
2.点蓝色部分RelativeLayout:RelativeLayout的OntouchEvent返回false表示不消费事件;ACTION_UP不传给他了
由于触摸事件都是连续的。如果ACTION_DOWN传递到子控件,而子控件的onTouchEvent返回值是false,即没有处理该ACTION_DOWN事件,则后续的ACTION_MOVE和ACTION_UP就不会传到该子控件来了。ACTION_DOWN事件一直传递到了RelativeLayout,但是最终是被MainActivity的onTouchEvent处理的,而从而导致ACTION_MOVE和ACTION_UP只传递到了MainActivity,最终也是由MainActivity处理的情况。
引用图片:https://blog.****.net/morgan_xww/article/details/9372285
3.点淡绿色部分MyTextView:MyTextView返回true;ACTION_UP继续传给他
逻辑示意
引用图片:https://blog.****.net/morgan_xww/article/details/9372285
4.点淡蓝色部分MyButton:MyButton返回false;MyTextView返回true; ACTION_UP不再通过MyButton
由于触摸事件都是连续的。如果ACTION_DOWN传递到子控件,而子控件的onTouchEvent返回值是false,即没有处理该ACTION_DOWN事件,则后续的ACTION_MOVE和ACTION_UP就不会传到该子控件来了。ACTION_DOWN事件一直传递到了MyButton,但是最终是被MyTextView的onTouchEvent处理的,而从而导致ACTION_MOVE和ACTION_UP只传递到了MyTextView,最终也是由MyTextView处理而跳过MyButton的情况。
参考这个