Android中触摸事件(Touch Event)传递机制

Android面试技术要点汇总
2021-05-17 14:29 · 阅读时长18分钟
小课

在Android中,触摸事件产生后会封装成一个MotionEvent对象进行传递,MotionEvent包含触摸事件的动作类型,坐标等信息,一些常用的动作类型包括

  • ACTION_DOWN手指开始触摸屏幕,触摸事件的开始动作类型。
  • ACTION_UP手指离开屏幕,触摸事件的结束动作类型。
  • ACTION_MOVE手指在屏幕上滑动,不断滑动会不断的产生该动作类型的事件。
  • ACTION_POINTER_DOWN多点触摸时,非第一个手指触摸屏幕时触发,MotionEvent会包含当前是第几个手指。
  • ACTION_POINTER_UP多点触摸时,非第一个手指离开屏幕触发,MotionEvent会包含当前是第几个手指。

当触摸事件产生后传递到当前Activity,通过Activity的dispatchTouchEvent方法开始分发事件。首先介绍事件传递中的几个重要的方法,后面再通过几个实例来看看传递过程,文章末尾会附上整个示例项目。

dispatchTouchEvent

这个方法是在View中定义的,它的返回值表示当前View是否消费了该事件,ViewGroup重写了这个方法,也就是它们俩的处理逻辑是不同的。

  • View的dispatchTouchEvent方法,首先是判断当前View是否有OnTouchListener,以及它是否会消费事件,如果没消费则回调当前View的onTouchEvent方法,否则不回调。只要OnTouchListener或onTouchEvent其中之一消费了该事件就会返回true。
  • ViewGroup中的dispatchTouchEvent方法,首先是判断是否要拦截向子View传递事件,如果不拦截的话,会循环调用子View的dispatchTouchEvent,如果有子View的dispatchTouchEvent有返回true的,说明其消费了该事件,则跳出循环,返回true,且不会再回调ViewGroup的OnTouchListener和onTouchEvent。如果拦截或者没有子View消费事件,则后续逻辑与View中的一致。
onInterceptTouchEvent

这个方法是在ViewGroup中定义,View中没有该方法,它的返回值表示是否要拦截向子View传递事件,在ViewGroup中的dispatchTouchEvent用到。子View可以通过调用父View的requestDisallowInterceptTouchEvent方法来阻止拦截。

onTouchEvent

这个方法是在View中定义的,它的返回值表示是否消费了该事件,默认的实现处理了一些状态变化,触发了点击、长按等事件。

如果View对该事件做了处理,比如setOnContextClickListener、setOnLongClickListener、setOnClickListener,甚至setClickable都会消费事件,导致View的onTouchEvent回调返回true,进而导致View的dispatchTouchEvent返回true,当然也包括直接重写onTouchEvent和dispatchTouchEvent方法使其返回true。

场景一

Test1Activity中包含一个ViewGroup(tag=Layout),Layout中包含一个View(tag=Text1),它们都不消费事件也不拦截事件,当点击Text1,我们打印日志观察一下事件传递路径,我们使用测试集合1来进行测试。

Android中触摸事件(Touch Event)传递机制

当点击Text1时,打印日志如下。

12022-04-20 18:23:05.459 D: Test1Activity    dispatchTouchEvent    ACTION_DOWN
22022-04-20 18:23:05.465 D: Layout           dispatchTouchEvent    ACTION_DOWN
32022-04-20 18:23:05.466 D: Layout           onInterceptTouchEvent ACTION_DOWN
42022-04-20 18:23:05.466 D: Text1            dispatchTouchEvent    ACTION_DOWN
52022-04-20 18:23:05.466 D: Text1            onTouchEvent          ACTION_DOWN
62022-04-20 18:23:05.467 D: Layout           onTouchEvent          ACTION_DOWN
72022-04-20 18:23:05.468 D: Test1Activity    onTouchEvent          ACTION_DOWN
82022-04-20 18:23:05.769 D: Test1Activity    dispatchTouchEvent    ACTION_MOVE
92022-04-20 18:23:05.770 D: Test1Activity    onTouchEvent          ACTION_MOVE
102022-04-20 18:23:05.837 D: Test1Activity    dispatchTouchEvent    ACTION_MOVE
112022-04-20 18:23:05.837 D: Test1Activity    onTouchEvent          ACTION_MOVE
122022-04-20 18:23:05.911 D: Test1Activity    dispatchTouchEvent    ACTION_UP
132022-04-20 18:23:05.911 D: Test1Activity    onTouchEvent          ACTION_UP

从日志中可以看出,ACTION_DOWN从Test1Activity.dispatchTouchEvent开始,一直到最底层的Text1.onTouchEvent,然后再回到Test1Activity.onTouchEvent。另外一点,因为没有View消费ACTION_DOWN事件,后续的ACTION_MOVE和ACTION_UP事件都不再向下传递

场景二

Test1Activity中包含一个ViewGroup(tag=Layout),Layout中包含一个View(tag=Text2),Layout不拦截事件,Text2会消费事件(setOnClickListener),当点击Text2,我们打印日志观察一下事件传递路径,我们使用测试集合1来进行测试。

Android中触摸事件(Touch Event)传递机制

当点击Text2时,打印日志如下。

12022-04-21 13:33:17.008 D: Test1Activity    dispatchTouchEvent    ACTION_DOWN
22022-04-21 13:33:17.009 D: Layout           dispatchTouchEvent    ACTION_DOWN
32022-04-21 13:33:17.010 D: Layout           onInterceptTouchEvent ACTION_DOWN
42022-04-21 13:33:17.010 D: Text2            dispatchTouchEvent    ACTION_DOWN
52022-04-21 13:33:17.011 D: Text2            onTouchEvent          ACTION_DOWN
62022-04-21 13:33:17.110 D: Test1Activity    dispatchTouchEvent    ACTION_UP
72022-04-21 13:33:17.110 D: Layout           dispatchTouchEvent    ACTION_UP
82022-04-21 13:33:17.111 D: Layout           onInterceptTouchEvent ACTION_UP
92022-04-21 13:33:17.111 D: Text2            dispatchTouchEvent    ACTION_UP
102022-04-21 13:33:17.111 D: Text2            onTouchEvent          ACTION_UP
112022-04-21 13:33:17.116 D: Text2            onClick  

从日志中可以看出,ACTION_DOWN从Test1Activity.dispatchTouchEvent开始,一直到最底层的Text2.onTouchEvent,但是由于Text2消费了事件,所以事件传递到此为止,不再向上回调父View的onTouchEvent,并且ACTION_UP也会传递到Text2.onTouchEvent,同样到此为止,最终触发onClick回调。

场景三

Test1Activity中包含一个ViewGroup(tag=Layout),Layout中包含一个View(tag=Text2),Layout不拦截事件,Text2会消费事件(setOnClickListener),布局与场景二一样,但是操作过程不一样,在手指接触Text2后,不直接抬起,而是等手指滑动到Text2外部再抬起。我们打印日志观察一下事件传递路径,我们使用测试集合1来进行测试。

Android中触摸事件(Touch Event)传递机制

操作完成之后,打印日志如下。

12022-04-21 13:47:55.503 D: Test1Activity    dispatchTouchEvent    ACTION_DOWN
22022-04-21 13:47:55.503 D: Layout           dispatchTouchEvent    ACTION_DOWN
32022-04-21 13:47:55.503 D: Layout           onInterceptTouchEvent ACTION_DOWN
42022-04-21 13:47:55.504 D: Text2            dispatchTouchEvent    ACTION_DOWN
52022-04-21 13:47:55.504 D: Text2            onTouchEvent          ACTION_DOWN
62022-04-21 13:47:55.764 D: Test1Activity    dispatchTouchEvent    ACTION_MOVE
72022-04-21 13:47:55.764 D: Layout           dispatchTouchEvent    ACTION_MOVE
82022-04-21 13:47:55.764 D: Layout           onInterceptTouchEvent ACTION_MOVE
92022-04-21 13:47:55.764 D: Text2            dispatchTouchEvent    ACTION_MOVE
102022-04-21 13:47:55.764 D: Text2            onTouchEvent          ACTION_MOVE
11········
122022-04-21 13:47:56.081 D: Test1Activity    dispatchTouchEvent    ACTION_MOVE
132022-04-21 13:47:56.081 D: Layout           dispatchTouchEvent    ACTION_MOVE
142022-04-21 13:47:56.081 D: Layout           onInterceptTouchEvent ACTION_MOVE
152022-04-21 13:47:56.082 D: Text2            dispatchTouchEvent    ACTION_MOVE
162022-04-21 13:47:56.082 D: Text2            onTouchEvent          ACTION_MOVE
172022-04-21 13:47:56.091 D: Test1Activity    dispatchTouchEvent    ACTION_UP
182022-04-21 13:47:56.092 D: Layout           dispatchTouchEvent    ACTION_UP
192022-04-21 13:47:56.092 D: Layout           onInterceptTouchEvent ACTION_UP
202022-04-21 13:47:56.092 D: Text2            dispatchTouchEvent    ACTION_UP
212022-04-21 13:47:56.092 D: Text2            onTouchEvent          ACTION_UP

从日志中可以看出,ACTION_DOWN从Test1Activity.dispatchTouchEvent()开始,一直到最底层的Text2.onTouchEvent,由于Text2消费了事件,所以事件传递到此为止,不再向上回调父View的onTouchEvent,后面触发的ACTION_MOVE和ACTION_UP也同样到Text2.onTouchEvent为止,最终没有触发onClick回调,这是因为在ACTION_MOVE事件传递时,在Text2.onTouchEvent会判断该事件的坐标是否在当前View内部,如果有出现过在View外面,则在ACTION_UP中不再回调onClick

场景四

Test2Activity中包含一个ScrollView(tag=Scroll),ScrollView中有一个Layout,Layout中包含一个View(tag=Text1),Text1会消费事件(setOnClickListener),进行以下操作,在手指接触Text1后,不直接抬起,而是向上滑动一段距离再抬起手指。我们打印日志观察一下事件传递路径,我们使用测试集合2来进行测试。

Android中触摸事件(Touch Event)传递机制

当操作完成后,打印日志如下。

12022-04-21 14:31:38.539 D: Test2Activity    dispatchTouchEvent    ACTION_DOWN
22022-04-21 14:31:38.541 D: Scroll           dispatchTouchEvent    ACTION_DOWN
32022-04-21 14:31:38.541 D: Scroll           onInterceptTouchEvent ACTION_DOWN
42022-04-21 14:31:38.541 D: Text1            dispatchTouchEvent    ACTION_DOWN
52022-04-21 14:31:38.541 D: Text1            onTouchEvent          ACTION_DOWN
62022-04-21 14:31:38.829 D: Test2Activity    dispatchTouchEvent    ACTION_MOVE
72022-04-21 14:31:38.829 D: Scroll           dispatchTouchEvent    ACTION_MOVE
82022-04-21 14:31:38.830 D: Scroll           onInterceptTouchEvent ACTION_MOVE
92022-04-21 14:31:38.830 D: Text1            dispatchTouchEvent    ACTION_MOVE
102022-04-21 14:31:38.830 D: Text1            onTouchEvent          ACTION_MOVE
11········
122022-04-21 14:31:38.880 D: Test2Activity    dispatchTouchEvent    ACTION_MOVE
132022-04-21 14:31:38.880 D: Scroll           dispatchTouchEvent    ACTION_MOVE
142022-04-21 14:31:38.880 D: Scroll           onInterceptTouchEvent ACTION_MOVE
152022-04-21 14:31:38.880 D: Text1            dispatchTouchEvent    ACTION_MOVE
162022-04-21 14:31:38.881 D: Text1            onTouchEvent          ACTION_MOVE
172022-04-21 14:31:38.897 D: Test2Activity    dispatchTouchEvent    ACTION_MOVE
182022-04-21 14:31:38.897 D: Scroll           dispatchTouchEvent    ACTION_MOVE
192022-04-21 14:31:38.897 D: Scroll           onInterceptTouchEvent ACTION_MOVE
202022-04-21 14:31:38.897 D: Text1            dispatchTouchEvent    ACTION_CANCEL
212022-04-21 14:31:38.897 D: Text1            onTouchEvent          ACTION_CANCEL
222022-04-21 14:31:38.914 D: Test2Activity    dispatchTouchEvent    ACTION_MOVE
232022-04-21 14:31:38.914 D: Scroll           dispatchTouchEvent    ACTION_MOVE
242022-04-21 14:31:38.914 D: Scroll           onTouchEvent          ACTION_MOVE
25········
262022-04-21 14:31:39.165 D: Test2Activity    dispatchTouchEvent    ACTION_MOVE
272022-04-21 14:31:39.165 D: Scroll           dispatchTouchEvent    ACTION_MOVE
282022-04-21 14:31:39.165 D: Scroll           onTouchEvent          ACTION_MOVE
292022-04-21 14:31:39.233 D: Test2Activity    dispatchTouchEvent    ACTION_UP
302022-04-21 14:31:39.234 D: Scroll           dispatchTouchEvent    ACTION_UP
312022-04-21 14:31:39.234 D: Scroll           onTouchEvent          ACTION_UP

从日志中可以看出,ACTION_DOWN从Test2Activity.dispatchTouchEvent开始,一直到最底层的Text1.onTouchEvent,由于Text1消费了事件,所以事件传递到此为止,后面紧跟的ACTION_MOVE事件也同样到Text1.onTouchEvent为止,但是在往后出现ACTION_CANCEL事件之后,ACTION_MOVE和ACTION_UP都不再传递到Text1。

首先先解释一下,这里为什么会出现ACTION_CANCEL事件。在ACTION_DOWN到达Text1.onTouchEvent之后,因为Text1设置了点击事件,也就是消费了事件,所以后面的ACTION_MOVE事件也会传递到Text1,但是在ACTION_MOVE事件传递时,ScrollView会认定这是在滑动,所以它会通过onInterceptTouchEvent拦截后续事件,在拦截之前它会先通过ACTION_CANCEL事件来通知到之前消费事件的View。

加载中...
dispatchTouchEventonInterceptTouchEventonTouchEvent