研究了下 Android 可视组件的事件处理机制,本想用文字来阐述,但是发现太复杂,文字不太适合用来表达逻辑,遂改用程序代码来表述。读完本程序,你将会对 Android UI 事件处理机制有一个全新的认识。若能充分利用事件传递的这些特性,你写自定义组件就可以随心所欲,游刃有余了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
|
package com.test; import android.view.MotionEvent; import android.view.View; /** * Android可视组件的触摸事件传递是通过调用dispatchTouchEvent(MotionEvent event) * 或dispatchKeyEvent(KeyEvent event)来实现事件的捕获、目标和冒泡三个阶段,调用是从最父层的组件开始,如Activity。 * 不过这里仅阐述了触摸点击事件,至于键盘事件则比较简单,暂不赘述。 * * 这是一个对Android事件机制的模拟,读完本程序,你便明白了事件的处理过程。这里包含了几种不同组件中事件的传递和处理方式。 * 这里作了一个融合,而在实际API类当中,根据不同的类有不同的实现方法,具体请看源码,不过我没来得及看。 * * 注意不要试图运行本程序,本程序只是表达了执行逻辑,个人认为用程序比用文字的方式表达的更加清晰。 * 程序是最好的语言嘛,对于程序员来说。 */ public class Android事件模拟 { /**是否应该向子组件传递事件**/ boolean childConsume = false ; /**本组件是Activity**/ boolean isActivity = true | false ; /**本组件是ViewGroup**/ boolean isViewGroup = true | false ; /**本组件是View而不是ViewGroup**/ boolean isView = true | false ; /**子组件是View(包括ViewGroup)**/ boolean childIsView = true | false ; /**有无子组件或者事件源是否在直接或间接子组件上**/ boolean hasChild = true | false ; /**模拟子视图组件**/ Child child = new Child(); /**外部监听器**/ View.OnTouchListener touchListener = null ; View.OnLongClickListener longClickListener = null ; View.OnClickListener clickListener = null ; /**本类对象,非Activity**/ View view = null ; /**默认状态下触摸事件的逻辑处理方法,当前对象层级视图组件触摸事件处理的入口**/ public boolean dispatchTouchEvent(MotionEvent event) { /**本方法的返回值**/ boolean b = false ; if (isActivity) { //如果本对象是Activity。//完全由onTouchEvent()决定本方法的返回值 if (event.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); childConsume = hasChild ? true : false ; if (childConsume) { if (child.dispatchTouchEvent(event)) { //注意子组件的dispatchTouchEvent(event)处理过程跟本方法相同 childConsume = true ; b = true ; } else { childConsume = false ; b = onTouchEvent(event); } } else { b = onTouchEvent(event); } } else if (childConsume) { //即使后续事件中child.dispatchTouchEvent(event)返回false,也继续传递 if (child.dispatchTouchEvent(event)) { b = true ; } else { b = onTouchEvent(event); } } else { b = onTouchEvent(event); } } else if (isViewGroup) { //如果本对象是ViewGroup boolean intercept = false ; if (event.getAction() == MotionEvent.ACTION_DOWN) { intercept = onInterceptTouchEvent(event); //不管有没有子组件,本方法都将执行 childConsume = (hasChild && ! intercept) ? true : false ; if (childConsume) { if (child.dispatchTouchEvent(event)) { childConsume = true ; b = true ; } else { childConsume = false ; if (touchListener != null ) { b = touchListener.onTouch(view, event); //调用外部监听器 } if (!b) b = onTouchEvent(event); } } else { childConsume = false ; if (touchListener != null ) { b = touchListener.onTouch(view, event); } if (!b) b = onTouchEvent(event); } } else if (event.getAction() == MotionEvent.ACTION_MOVE) { if (childConsume) { intercept = onInterceptTouchEvent(event); if (intercept) { childConsume = false ; event.setAction(MotionEvent.ACTION_CANCEL); //重点,后续事件被拦截,将变为取消事件并继续传递 child.dispatchTouchEvent(event); b = true ; //此时不论child.dispatchTouchEvent(event)返回什么值,本方法都直接返回true } else { b = child.dispatchTouchEvent(event); //即使返回false也直接返回,区别于Activity } } else { if (touchListener != null ) { b = touchListener.onTouch(view, event); } if (!b) b = onTouchEvent(event); } } else if (event.getAction() == MotionEvent.ACTION_UP) { if (childConsume) { intercept = onInterceptTouchEvent(event); if (intercept) { childConsume = false ; event.setAction(MotionEvent.ACTION_CANCEL); //重点,后续事件被拦截,将变为取消事件并继续传递 child.dispatchTouchEvent(event); b = true ; //此时不管child.dispatchTouchEvent(event)返回什么值,本方法都直接返回true } else { b = child.dispatchTouchEvent(event); //即时返回false也直接返回,区别于Activity } } else { if (touchListener != null ) { b = touchListener.onTouch(view, event); } if (!b) b = onTouchEvent(event); //注意onClick事件是在onTouchEvent()内部处理的 } } } else if (isView) { //如果本对象是不能添加子组件的View,如:Button、EditText if (touchListener != null ) { b = touchListener.onTouch(view, event); } if (!b) b = onTouchEvent(event); } return b; } /**Activity类特有,重写它,可用于在事件捕获阶段,事件到达本层容器而未进行任何其他处理之前做些事情**/ public void onUserInteraction() {} /**ViewGroup类特有,其返回值表示是否拦截事件,以决定事件是否继续传递**/ public boolean onInterceptTouchEvent(MotionEvent event) { return true | false ; } /**Activity默认返回false,View默认返回true。返回值会决定后续的事件传递情况。可以根据需要自定义返回值。**/ public boolean onTouchEvent(MotionEvent event) { /* * 当Activity收到MotionEvent.ACTION_DOWN事件的冒泡返回,则会启动一个LongClick计时线程,之后若View组件树中 * 有任何一个组件在延时之内收到MotionEvent.ACTION_UP或MotionEvent.ACTION_CANCEL事件,则计时终止。 * 因此LongClick事件在两种状况下触发: * * a、若父级Activity收到child.dispatchTouchEvent(event)的返回值为false时; * b、长按某组件,MotionEvent.ACTION_UP或MotionEvent.ACTION_CANCEL事件发生在LongClick之后时。 * * LongClick事件触发时,从叶子层组件向根层组件依次调用View.OnLongClickListener.onLongClick(View v)方法。 * 该方法优先于组件本身默认的longClick相关处理方法,若返回值为false,会使组件继续调用默认的处理方法, * 否则不执行默认的处理方法并给View.OnClickListener.onClick(View v)一个不要执行的标识。 * 但返回值不会影响上层组件对该方法的调用。 */ switch (event.getAction()) { case MotionEvent.ACTION_DOWN: /* Post本对象到LongClick计时线程 */ break ; case MotionEvent.ACTION_MOVE: break ; case MotionEvent.ACTION_UP: /* 终止LongClick计时线程 */ /* 执行View.OnClickListener.onClick(View v)。若View.OnLongClickListener.onLongClick(View v)先执行, * 则根据其返回值决定是否执行View.OnClickListener.onClick(View v) */ break ; } return true | false ; } /**添加外部监听器。View类特有(包括ViewGroup),注意无论调用多少次本方法,只有最后一个监听器起作用,即覆盖**/ public void setOnTouchListener(View.OnTouchListener l) { touchListener = l; } /**添加外部监听器。View类特有(包括ViewGroup),注意无论调用多少次本方法,只有最后一个监听器起作用,即覆盖**/ public void setOnClickListener(View.OnClickListener l) { clickListener = l; } /**添加外部监听器。View类特有(包括ViewGroup),注意无论调用多少次本方法,只有最后一个监听器起作用,即覆盖**/ public void setOnLongClickListener(View.OnLongClickListener l) { longClickListener = l; } } class Child extends Android事件模拟 {} |