前言
本文适合对触摸事件有粗略了解的新手梳理逻辑使用,资深玩家请绕路通行。
内容整理自《Android开发艺术探索》,对书中内容摘要概括,未提及源码分析部分。事件分发图解参考自wangkuiwu' Homepage。
本文区分ViewGroup与View,下文中使用的View均代表无子元素的单个View,非ViewGroup。
流程梳理
首先,我们要明确在事件分发中涉及到的重要的三个方法:
-
[分发事件] public boolean dispatchTouchEvent (MotionEvent event)
如果事件能够传递给当前View(或ViewGroup),那么此方法一定会被调用。返回结果受当前View(或ViewGroup)的onTouchEvent和下级View(或ViewGroup)的dispatchTouchEvent方法的影响,表示是否继续分发当前事件。
-
[拦截事件] public boolean onInterceptTouchEvent (MotionEvent event)
在dispatchTouchEvent方法内部调用,用来判断是否拦截这个事件,如果当前ViewGroup拦截了某个事件,那么在同一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件。
[处理事件] public boolean onTouchEvent (MotionEvent event)
在dispatchTouchEvent方法内部调用,用来处理点击事件。返回结果表示是否消耗当前事件。如果不消耗,则在同一个事件序列中,当前View无法再次接收到事件。
可以用伪代码形象的表示这三个方法之间的关系:
public boolean dispatchTouchEvent(MotionEvent event) (
boolean consume = false;
if (onInterceptTouchEvent(event)) {
consume = onTouchEvent(event);
} else {
consume = child.dispatchTouchEvent(event);
}
return consume;
}
同一个事件序列是指从手指触摸屏幕开始,到手指离开屏幕为止,这个过程中产生的一系列事件。
在View中包含以下两个方法:
public boolean dispatchTouchEvent(MotionEvent ev);
public boolean onTouchEvent(MotionEvent ev);
在ViewGroup中包含以下三个方法:
public boolean dispatchTouchEvent(MotionEvent ev);
public boolean onInterceptTouchEvent(MotionEvent ev);
public boolean onTouchEvent(MotionEvent ev);
在Activity中包含以下两个方法:
public boolean dispatchTouchEvent(MotionEvent ev);
public boolean onTouchEvent(MotionEvent ev);
Activity事件分发
触摸屏幕后,点击事件的分发顺序是Activity - window(PhoneWindow) - 顶层ViewGroup(DecorView) - ... - View
。Activity通过dispatchTouchEvent分发事件,如果子View中都未处理事件,那么事件将交给Activity的onTouchEvent方法进行处理。这部分暂时不重点展开,了解即可。
ViewGroup事件分发
点击事件达到ViewGroup后,调用ViewGroup的dispatchTouchEvent方法,然后:
- 如果ViewGroup拦截事件(即onInterceptTouchEvent返回true),则事件由ViewGroup处理。
- ViewGroup设置了onTouchListener:
调用onTouchListener的onTouch方法,如果onTouch消耗了事件(onTouch返回true),则onTouchEvent不会再被调用。如果onTouch未消耗事件,继续调用onTouchEvent方法。 - ViewGroup未设置onTouchListener:
调用onTouchEvent方法,如果设置了onClickListener,则onClick会被调用。
- ViewGroup设置了onTouchListener:
- 如果ViewGroup不拦截事件(即onInterceptTouchEvent返回false),则事件会传递给它所在的点击事件链上的子View(或ViewGroup),这时子View(或ViewGroup)的dispatchTouchEvent会被调用。到此为止,事件己经从ViewGroup传递给了下一层View(或ViewGroup),接下来的传递过程均如此循环,完成整个事件的分发。
如果ViewGroup拦截了事件,但是最终没有消耗事件,那么事件会被交给上一级的ViewGroup进行处理。
View事件分发
因为View(不包含ViewGroup)是单独的元素,它没有子元素因此无法向下传递事件,所以它只能选择是否自己处理事件。点击事件通过View的dispatchTouchEvent方法进行分发,然后:
-
如果View设置了onTouchListener:
- 如果onTouchListener的onTouch方法返回true,那么onTouchEvent不会被调用。
- 如果onTouchListener的onTouch方法返回false,那么onTouchEvent会被调用。
-
如果View未设置onTouchListener:
- 调用onTouchEvent方法。
View中不包含onInterceptTouchEvent方法,onTouchListener的优先级高于onTouchEvent。
onTouchEvent方法的实现:
只要View的CLICKABLE和LONG_CLICKABLE有一个为true,那么它就会消耗这个事件,即onTouchEvent方法返回true,不管它是不是DISABLE的状态。反之,返回false,不消耗事件。
ACTION_UP事件发生时,会触发performClick方法,如果View设置了onClickListener,那么performClick方法内部会调用它的onClick方法。即onClick方法是在onTouchEvent中回调的。
View的LONG_CLICKABLE属性: 默认为false,可以通过setLongClickable和setOnLongClickListener改变LONG_CLICKABLE属性的值。
View的CLICKABLE属性: 与具体的View相关,比如Button默认是可点击的,所以CLICKABLE默认为true, TextView默认是不可点击的,所以CLICKABLE默认为false。可以通过setClickable和setOnClickListener改变CLICKABLE属性的值。
如果View最终没有消耗事件,那么事件会被交给上一级的ViewGroup进行处理。