在Android中,触摸事件产生后会封装成一个MotionEvent对象进行传递,MotionEvent包含触摸事件的动作类型,坐标等信息,一些常用的动作类型包括
ACTION_DOWN
手指开始触摸屏幕,触摸事件的开始动作类型。ACTION_UP
手指离开屏幕,触摸事件的结束动作类型。ACTION_MOVE
手指在屏幕上滑动,不断滑动会不断的产生该动作类型的事件。ACTION_POINTER_DOWN
多点触摸时,非第一个手指触摸屏幕时触发,MotionEvent会包含当前是第几个手指。ACTION_POINTER_UP
多点触摸时,非第一个手指离开屏幕触发,MotionEvent会包含当前是第几个手指。当触摸事件产生后传递到当前Activity,通过Activity的dispatchTouchEvent方法开始分发事件。首先介绍事件传递中的几个重要的方法,后面再通过几个实例来看看传递过程,文章末尾会附上整个示例项目。
这个方法是在View中定义的,它的返回值表示当前View是否消费了该事件,ViewGroup重写了这个方法,也就是它们俩的处理逻辑是不同的。
这个方法是在ViewGroup中定义,View中没有该方法,它的返回值表示是否要拦截向子View传递事件,在ViewGroup中的dispatchTouchEvent用到。子View可以通过调用父View的requestDisallowInterceptTouchEvent方法来阻止拦截。
这个方法是在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
来进行测试。
当点击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
来进行测试。
当点击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
来进行测试。
操作完成之后,打印日志如下。
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
来进行测试。
当操作完成后,打印日志如下。
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。
文件 | 大小 | 修改时间 |
---|---|---|
app | 2022年04月21日 | |
build.gradle | 358 B | 2022年04月20日 |
gradle/wrapper | 2022年04月20日 | |
gradle.properties | 1 kB | 2022年04月20日 |
gradlew | 6 kB | 2022年04月20日 |
gradlew.bat | 3 kB | 2022年04月20日 |
local.properties | 437 B | 2022年04月20日 |
settings.gradle | 327 B | 2022年04月20日 |