一、简介
触摸事件就是捕获触摸屏幕后产生的事件。Android为触摸事件封装了一个类——MotionEvent,如果重写onTouchEvent(MotionEvent event)方法,就会发现该方法的参数就是一个MotionEvent类实例。
事件触发分为三个阶段,捕获、目标、冒泡。
第一个阶段为事件捕获,事件从根节点流向目标对象节点。途中经过各个层次的View节点,并在各节上触发捕获事件,直到到达事件的目标节点。
第二个阶段为目标阶段,事件进入到目标阶段,事件在目标节点上被触发,然后会逆向回流,直到传播至最外层的根节点。
第三个阶段为事件冒泡,事件在目录View节点上触发后,并不在这个元素上终止,它会随着节点树一层层向上冒泡,回溯到根节点上。
二、事件拦截机制分析
Android的View结构就是一个树形结构,View可以放在一个ViewGroup里面,这个ViewGroup又放在另一个ViewGroup里面,甚至还有可能继续嵌套,一层层地叠起来。触摸事件就一个,到底该分给谁?同一个事件,子View和父ViewGroup都有可能想要进行处理,因此就产生了“事件拦截机制”。
假设,你所在公司,有一个总经理,级别最高;他下面有一个部长,级别次之;最低层,就是干活的你,没有级别。现在董事会交给总经理一个任务,总经理将这项任务布置给了部长,部长又把任务安排给了你。而当你完成了这项任务,你就把任务交给部长,部长觉得任务完成的不错,于是就签了他的名字,将任务交给了总经理,总经理看了也觉得不错,就也签了名字交给董事会。这个,一个任务就顺利完成了。
一个总经理——MyViewGroupA,最外层的ViewGroup;
一个部长——MyViewGroupB,中间层ViewGroup;
一个干活的你——MyView,在最低层;
对于ViewGroup,需要重写三个方法:
1 @Override
2 public boolean dispatchTouchEvent(MotionEvent event)
3 {
4 Log.d(TAG, "ViewGroup dispatchTouchEvent" + event.getAction());
5 return super.dispatchTouchEvent(event);
6 }
7
8 @Override
9 public boolean onInterceptTouchEvent(MotionEvent event)
10 {
11 Log.d(TAG, "ViewGroup onInterceptTouchEvent" + event.getAction());
12 return super.onInterceptTouchEvent(event);
13 }
14
15 @Override
16 public boolean onTouchEvent(MotionEvent event)
17 {
18 Log.d(TAG, "ViewGroup onTouchEvent" + event.getAction());
19 return super.onTouchEvent(event);
20 }
对于View,需要重写如下两个方法:
1 @Override
2 public boolean dispatchTouchEvent(MotionEvent event)
3 {
4 Log.d(TAG, "View dispatchTouchEvent" + event.getAction());
5 return super.dispatchTouchEvent(event);
6 }
7
8 @Override
9 public boolean onTouchEvent(MotionEvent event)
10 {
11 Log.d(TAG, "View onTouchEvent" + event.getAction());
12 return super.onTouchEvent(event);
13 }
当点击View后的Log如下所示:
1 D/MyViewGroupA : ViewGroupA dispatchTouchEvent
2 D/MyViewGroupA : ViewGroupA onInterceptTouchEvent
3 D/MyViewGroupB : ViewGroupB dispatchTouchEvent
4 D/MyViewGroupB : ViewGroupB onInterceptTouchEvent
5 D/MyView : View dispatchTouchEvent
6 D/MyView : View onTouchEvent
7 D/MyViewGroupB : MyViewGroupB onTouchEvent
8 D/MyViewGroupA : MyViewGroupA onTouchEvent
从其中可以看到,事件传递顺序是:
总经理(MyViewGroupA)— 部长(MyViewGroupB)— 你(MyView),这个过程就是事件的捕获阶段;
PS:事件传递,即捕获,就是执行dispatchTouchEvent()方法,再执行onInterceptTouchEvent()方法。
事件的处理顺序:
在你去干活的过程就是事件的目标阶段,即View的onTouchEvent()方法执行;
冒泡阶段;
PS:事件处理就是执行onTouchEvent()方法。
假设1:总经理(MyViewGroupA)发现这个任务太简单了,觉得自己就可以完成,完全没必要再找下属。因此,事件就被总经理(MyViewGroupA)onInterceptTouchEvent()方法把事件拦截了,即让onInterceptTouchEvent()方法返回值为True,下面再看看Log:
1 D/MyViewGroupA : ViewGroupA dispatchTouchEvent
2 D/MyViewGroupA : ViewGroupA onInterceptTouchEvent
3 D/MyViewGroupA : MyViewGroupA onTouchEvent
假设2:部长(MyViewGroupB)发现这个任务太简单了,觉得自己就可以完成,完全没必要再找下属。因此,事件就被部长(MyViewGroupB)onInterceptTouchEvent()方法把事件拦截了,即让onInterceptTouchEvent()方法返回值为True,下面再看看Log:
1 D/MyViewGroupA : ViewGroupA dispatchTouchEvent
2 D/MyViewGroupA : ViewGroupA onInterceptTouchEvent
3 D/MyViewGroupB : ViewGroupB dispatchTouchEvent
4 D/MyViewGroupB : ViewGroupB onInterceptTouchEvent
5 D/MyViewGroupB : MyViewGroupB onTouchEvent
6 D/MyViewGroupA : MyViewGroupA onTouchEvent
这两种情况下,可以看到总经理或者部长,MyViewGroupA或者MyViewGroupB将事件拦截了。
事件的返回值含义:
当事件在传递的过程中,返回值:True,拦截,不继续;False,不拦截,继续流程,传递给下一级。
当事件在处理的过程中,返回值:True,处理了,不用审核了;False,交给上一级处理。
三、总结
1. dispatchTouchEvent(...):
作用:决定事件是否由onInterceptTouchEvent拦截处理;
当返回super.dispatchTouchEvent(...)时,由onInterceptTouchEvent来决定事件的流向,onInterceptTouchEvent返回值为false时,继续向子View分发事件,本View只处理ACTION_DOWN事件。
当onInterceptTouchEvent返回值为true时,不继续向子View分发事件,本View处理所有事件。
2. onInterceptTouchEvent(...):
作用:拦截事件,决定是否将事件传递给子View;
当返回值为false时,事件继续传递给子View;
当返回值为true时,事件交给onTouchEvent(...)处理,不再向子View传递。
3. onTouchEvent(MotionEvent event):
作用:事件最终到这个方法,由此方法处理事件;
当返回值为false时,事件继续向上传递给其父View的onTouchEvent()方法,直到根View的,一直传递到根View时,返回值都是false,也就是说在事件传递过程中没有View的onTouchEvent()方法返回true,此次手势就会结束,此次事件就会被取消。
当返回值为true时,此View处理所有事件,并且处理的事件不会再向上返回。也就是说在值为true时,此手势的所有事件都传递给此View的onTouchEvent()处理,包括ACTION_DOWN、ACTION_UP、ACTION_MOVE等事件。
四、举例说明
自定义三个View,分别为ViewEventA、ViewEventB、ViewEventC:
1 // ViewEventA
2 public class ViewEventA extends ViewGroup {
3 private static final String TAG = "ViewEventA";
4
5 public ViewEventA(Context context) {
6 super(context);
7 }
8
9 @Override
10 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
11 int count = getChildCount();
12 for (int idx = 0; idx < count; idx++) {
13 View child = getChildAt(idx);
14 int widthMeSpec = MeasureSpec.makeMeasureSpec(300, MeasureSpec.EXACTLY);
15 int heightmeSpec = MeasureSpec.makeMeasureSpec(300, MeasureSpec.EXACTLY);
16 child.measure(widthMeSpec, heightmeSpec);
17 }
18 int widthMeSpec = MeasureSpec.makeMeasureSpec(500, MeasureSpec.EXACTLY);
19 int heightmeSpec = MeasureSpec.makeMeasureSpec(500, MeasureSpec.EXACTLY);
20 setMeasuredDimension(widthMeSpec, heightmeSpec);
21 }
22
23 @Override
24 protected void onLayout(boolean changed, int l, int t, int r, int b) {
25 if (changed) {
26 int count = getChildCount();
27 for (int idx = 0; idx < count; idx++) {
28 View child = getChildAt(idx);
29 child.layout(0, 0, 300, 300);
30 }
31 }
32 }
33
34 @Override
35 public boolean dispatchTouchEvent(MotionEvent event) {
36 Log.d(TAG, "dispatchTouchEvent");
37 return super.dispatchTouchEvent(event);
38 }
39
40 @Override
41 public boolean onInterceptTouchEvent(MotionEvent event) {
42 Log.d(TAG, "onInterceptTouchEvent");
43 return super.onInterceptTouchEvent(event);
44 }
45
46 @Override
47 public boolean onTouchEvent(MotionEvent event) {
48 String action = "";
49 switch (event.getAction()) {
50 case MotionEvent.ACTION_DOWN:
51 action = "ACTION_DOWN";
52 break;
53 case MotionEvent.ACTION_MOVE:
54 action = "ACTION_MOVE";
55 break;
56 case MotionEvent.ACTION_UP:
57 action = "ACTION_UP";
58 break;
59 case MotionEvent.ACTION_CANCEL:
60 action = "ACTION_CANCEL";
61 break;
62 }
63 Log.d(TAG, "onTouchEvent: " + action);
64 return super.onTouchEvent(event);
65 }
66 }
67
68 // ViewEventB
69 public class ViewEventB extends ViewGroup {
70 private static final String TAG = "ViewEventB";
71
72 public ViewEventB(Context context) {
73 super(context);
74 }
75
76 @Override
77 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
78 int count = getChildCount();
79 for (int idx = 0; idx < count; idx++) {
80 View child = getChildAt(idx);
81 int width = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);
82 int height = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);
83 child.measure(width, height);
84 }
85
86 setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
87 }
88
89 @Override
90 protected void onLayout(boolean changed, int l, int t, int r, int b) {
91 if (changed) {
92 int count = getChildCount();
93 for (int idx = 0; idx < count; idx++) {
94 View child = getChildAt(idx);
95 child.layout(0, 0, 100, 100);
96 }
97 }
98 }
99
100 @Override
101 public boolean dispatchTouchEvent(MotionEvent event) {
102 Log.d(TAG, "dispatchTouchEvent");
103 return super.dispatchTouchEvent(event);
104 }
105
106 @Override
107 public boolean onInterceptTouchEvent(MotionEvent event) {
108 Log.d(TAG, "onInterceptTouchEvent");
109 return super.onInterceptTouchEvent(event);
110 }
111
112 @Override
113 public boolean onTouchEvent(MotionEvent event) {
114 String action = "";
115 switch (event.getAction()) {
116 case MotionEvent.ACTION_DOWN:
117 action = "ACTION_DOWN";
118 break;
119 case MotionEvent.ACTION_MOVE:
120 action = "ACTION_MOVE";
121 break;
122 case MotionEvent.ACTION_UP:
123 action = "ACTION_UP";
124 break;
125 case MotionEvent.ACTION_CANCEL:
126 action = "ACTION_CANCEL";
127 break;
128 }
129 Log.d(TAG, "onTouchEvent: " + action);
130 return super.onTouchEvent(event);
131 }
132 }
133
134 // ViewEventC
135 public class ViewEventC extends ViewGroup {
136 private static final String TAG = "ViewEventC";
137
138 public ViewEventC(Context context) {
139 super(context);
140 }
141
142 @Override
143 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
144 setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
145 }
146
147 @Override
148 protected void onLayout(boolean changed, int l, int t, int r, int b) {
149
150 }
151
152 @Override
153 public boolean dispatchTouchEvent(MotionEvent event) {
154 Log.d(TAG, "dispatchTouchEvent");
155 return super.dispatchTouchEvent(event);
156 }
157
158 @Override
159 public boolean onInterceptTouchEvent(MotionEvent event) {
160 Log.d(TAG, "onInterceptTouchEvent");
161 return super.onInterceptTouchEvent(event);
162 }
163
164 @Override
165 public boolean onTouchEvent(MotionEvent event) {
166 String action = "";
167 switch (event.getAction()) {
168 case MotionEvent.ACTION_DOWN:
169 action = "ACTION_DOWN";
170 break;
171 case MotionEvent.ACTION_MOVE:
172 action = "ACTION_MOVE";
173 break;
174 case MotionEvent.ACTION_UP:
175 action = "ACTION_UP";
176 break;
177 case MotionEvent.ACTION_CANCEL:
178 action = "ACTION_CANCEL";
179 break;
180 }
181 Log.d(TAG, "onTouchEvent: " + action);
182 return super.onTouchEvent(event);
183 }
184 }
场景1:
当本三个嵌套View中onTouchEvent()方法返回值均为false时,输出:
1 D/ViewEventA: dispatchTouchEvent
2 D/ViewEventA: onInterceptTouchEvent
3 D/ViewEventB: dispatchTouchEvent
4 D/ViewEventB: onInterceptTouchEvent
5 D/ViewEventC: dispatchTouchEvent
6 D/ViewEventC: onInterceptTouchEvent
7 D/ViewEventC: onTouchEvent: ACTION_DOWN
8 D/ViewEventB: onTouchEvent: ACTION_DOWN
9 D/ViewEventA: onTouchEvent: ACTION_DOWN
从结果看出onTouchEvent()方法返回值均为false直到根View,事件消失了,手势被取消了。
场景2:
当本三个嵌套View中第二个View的onTouchEvent()方法返回值为true时,中间View,输出:
1 D/ViewEventA: dispatchTouchEvent
2 D/ViewEventA: onInterceptTouchEvent
3 D/ViewEventB: dispatchTouchEvent
4 D/ViewEventB: onInterceptTouchEvent
5 D/ViewEventC: dispatchTouchEvent
6 D/ViewEventC: onInterceptTouchEvent
7 D/ViewEventC: onTouchEvent: ACTION_DOWN
8 D/ViewEventB: onTouchEvent: ACTION_DOWN
9 D/ViewEventA: dispatchTouchEvent
10 D/ViewEventA: onInterceptTouchEvent
11 D/ViewEventB: dispatchTouchEvent
12 D/ViewEventB: onTouchEvent: ACTION_MOVE
13 D/ViewEventA: dispatchTouchEvent
14 D/ViewEventA: onInterceptTouchEvent
15 D/ViewEventB: dispatchTouchEvent
16 D/ViewEventB: onTouchEvent: ACTION_MOVE
17 D/ViewEventA: dispatchTouchEvent
18 D/ViewEventA: onInterceptTouchEvent
19 D/ViewEventB: dispatchTouchEvent
20 D/ViewEventB: onTouchEvent: ACTION_UP
从结果看出事件在向上冒泡返回时,发现第二个View的onTouchEvent()方法返回值为true时,此View消化了此次事件,并且此手势的其它事件均由第二个View处理。在第二个事件ACTION_UP从根View向下传递时,直到第二个View消化了此次事件,并且不再向下传递事件,同时,在事件被消化后也未再传递此事件。
场景三:
在第二个View中使用onInterceptTouchEvent()方法拦截事件,即第二个View的onInterceptTouchEvent()方法返回值为true,结果输出:
1 D/ViewEventA: dispatchTouchEvent
2 D/ViewEventA: onInterceptTouchEvent
3 D/ViewEventB: dispatchTouchEvent
4 D/ViewEventB: onInterceptTouchEvent
5 D/ViewEventB: onTouchEvent: ACTION_DOWN
6 D/ViewEventA: onTouchEvent: ACTION_DOWN
从结果看出第二个View拦截了手势的ACTION_DOWN事件,事件未再向下(第三个)子View传递,再是在第二个View时,将事件交给了onTouchEvent()方法处理,但是由于第二个View的onTouchEvent()方法返回值为false,事件继续向上(父View)传递。发现没有View处理此次事件,手势被系统取消。
场景4:
当第二个View的onInterceptTouchEvent()方法和onTouchEvent()方法返回值均为true时,拦截事件并消化事件,结果输出:
1 D/ViewEventA: onInterceptTouchEvent
2 D/ViewEventB: dispatchTouchEvent
3 D/ViewEventB: onInterceptTouchEvent
4 D/ViewEventB: onTouchEvent: ACTION_DOWN
5 D/ViewEventA: dispatchTouchEvent
6 D/ViewEventA: onInterceptTouchEvent
7 D/ViewEventB: dispatchTouchEvent
8 D/ViewEventB: onTouchEvent: ACTION_MOVE
9 D/ViewEventA: dispatchTouchEvent
10 D/ViewEventA: onInterceptTouchEvent
11 D/ViewEventB: dispatchTouchEvent
12 D/ViewEventB: onTouchEvent: ACTION_UP
结果与场景二一致。