一. 简单的概念说明

  1. 安卓的事件传递机制,最重要的就是 dispatchTouchEvent()onInterceptTouchEvent()onTouchEvent() 三个方法。

  2. VIewGroup,这三个方法都拥有。View,只拥有dispatchTouchEvent()onTouchEvent() 两个方法。

  3. dispatchTouchEvent 负责分发事件,在 dispatchTouchEvent 里又会调用 onTouchEvent 表示执行事件,或者说消费事件。

  4. 一般情况下 ViewGroup 的 dispatchTouchEvent 方法比较好理解,就是分发给ViewGroup的子类去处理这个事件,然而为什么View也同样有这个方法呢?原因就是View 可以注册很多事件监听器,例如:单击事件(onClick)、长按事件(onLongClick)、触摸事件(onTouch),并且View自身也有 onTouchEvent 方法。

二. 简单的例子说明

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
public class MainActivity extends AppCompatActivity {

public static final String TAG = MainActivity.class.getSimpleName();

@SuppressLint("ClickableViewAccessibility")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

Button btn = findViewById(R.id.btn1);

//onTouch
btn.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d(TAG,"Button onTouch:"+event.getAction());

return false;
}
});

//onClick
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG,"Button onClick");
}
});

}
}
1
2
3
2020-03-23 00:30:58.107 13639-13639/swu.xl.a03_22_touch D/MainActivity: Button onTouch:0
2020-03-23 00:30:58.111 13639-13639/swu.xl.a03_22_touch D/MainActivity: Button onTouch:1
2020-03-23 00:30:58.127 13639-13639/swu.xl.a03_22_touch D/MainActivity: Button onClick

1. 从上面的例子可以看到,我们是先执行 OnTouch 方法,然后再执行 OnClick 方法。因此事件传递的顺序是先经过onTouch,再传递到onClick。

2. 对于 OnTouch 方法中,我们返回的是 false ,这代表什么意思呢?如果我们返回 true 会怎么样?

1
2
2020-03-23 09:24:54.062 14397-14397/swu.xl.a03_22_touch D/MainActivity: Button onTouch:0
2020-03-23 09:24:54.066 14397-14397/swu.xl.a03_22_touch D/MainActivity: Button onTouch:1

3. 返回为 true 后的测试结果如上,onClick 方法不再被执行。你可以先理解成onTouch方法返回true就认为这个事件被onTouch消费掉了,因而不会再继续向下传递。接下里将分析为什么?

三. dispatchTouchEvent 和 onTouch

1.触摸之后调用的方法

  • 首先你需要知道一点,只要你触摸到了任何一个控件,就一定会调用该控件的dispatchTouchEvent方法。

  • 那当我们去点击按钮的时候,就会去调用Button类里的dispatchTouchEvent方法,可是你会发现Button类里并没有这个方法,那么就到它的父类TextView里去找一找,你会发现TextView里也没有这个方法,那没办法了,只好继续在TextView的父类View里找一找,这个时候你终于在View里找到了这个方法

2. 2020年3月23日,View的dispatchTouchEvent源码

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
/**
* Pass the touch screen motion event down to the target view, or this
* view if it is the target.
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTouchEvent(MotionEvent event) {
//最前面这一段就是判断当前事件是否能获得焦点,如果不能获得焦点或者不存在一个View,那我们就直接返回False跳出循环
// If the event should be handled by accessibility focus first.
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}

//设置返回的默认值
boolean result = false;

//这段是系统调试方面,可以直接忽略
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}

//Android用一个32位的整型值表示一次TouchEvent事件,低8位表示touch事件的具体动作,比如按下,抬起,滑动,还有多点触控时的按下,抬起,这个和单点是区分开的,下面看具体的方法:
//1 getAction:触摸动作的原始32位信息,包括事件的动作,触控点信息
//2 getActionMasked:触摸的动作,按下,抬起,滑动,多点按下,多点抬起
//3 getActionIndex:触控点信息
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture

// 当我们手指按到View上时,其他的依赖滑动都要先停下
stopNestedScroll();
}

//过滤掉一些不合法的事件,比如当前的View的窗口被遮挡了。
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}

//ListenerInfo 是view的一个内部类 里面有各种各样的listener,例如OnClickListener,OnLongClickListener,OnTouchListener等等
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;

//首先判断如果监听li对象!=null 且我们通过setOnTouchListener设置了监听,即是否有实现OnTouchListener,如果有实现就判断当前的view状态是不是ENABLED,如果实现的OnTouchListener的onTouch中返回true,并处理事件
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
//如果满足这些条件那么返回true,这个事件就在此处理
// 意味着这个View需要事件分发
result = true;
}

//如果上一段判断的条件没有满足(没有在代码里面setOnTouchListener的话),就判断View自身的onTouchEvent方法有没有处理,没有处理最后返回false,处理了返回true;
if (!result && onTouchEvent(event)) {
result = true;
}
}

//系统调试分析相关,没有影响
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}

// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
//如果这是手势的结尾,则在嵌套滚动后清理
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}

return result;
}

① 不得不说,dispatchTouchEvent 的源码 比之前郭霖大佬那时候分析的源码改变了太多。

② 源码看起来似乎很多,但是我们只需要关注下面的代码即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//首先判断如果监听li对象!=null 且我们通过setOnTouchListener设置了监听,即是否有实现OnTouchListener,如果有实现就判断当前的view状态是不是ENABLED,如果实现的OnTouchListener的onTouch中返回true,并处理事件
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
//如果满足这些条件那么返回true,这个事件就在此处理
// 意味着这个View需要事件分发
result = true;
}

//如果上一段判断的条件没有满足(没有在代码里面setOnTouchListener的话),就判断View自身的onTouchEvent方法有没有处理,没有处理最后返回false,处理了返回true;
if (!result && onTouchEvent(event)) {
result = true;
}

③ 第一个判断。

  • 第一个条件是li != null && li.mOnTouchListener != null ,这个主要是用来判断是否有实现OnTouchListener。

  • 第二个条件是 (mViewFlags & ENABLED_MASK) == ENABLED 是判断当前点击的控件是否是enable的,按钮默认都是enable。

  • 第三个条件就比较关键了,li.mOnTouchListener.onTouch(this, event)。其实也就是去回调控件注册touch事件时的onTouch方法。也就是说如果我们在onTouch方法里返回true,就会让这三个条件全部成立,从而整个方法直接返回true。如果我们在onTouch方法里返回false,就会再去执行第二个判断。

④ 第二个判断

  • 第一个条件是 !result,也就是 result == false,说明事件没有被消费。

  • 第二个条件也比较关键了,onTouchEvent(event)。其实也就是去回调view的onTouchEvent方法。如果我们在onTouch方法Event里返回true,就会让这两个条件全部成立,从而整个方法直接返回true。

⑤ 总结

  • dispatchTouchEvent 方法中首先执行的就是 onTouch 方法,如果 onTouch 消费了该事件,就不会再执行 onTouchEvent。

  • 由此我们推测出 onClick 方法是在 onTouchEvent 方法中执行的。这样子就是可以说明 onTouch 比 onClick 优先执行,通过之前的两个判断顺序可以看出来。也可以知道 onTouch 方法 返回 true,为什么不执行 onClick。因为,第一个判断中将 result 设置为 true,而要进入第二个判断执行 onTouchEvent,就必须要 result 为 false。

3.2020年3月23日,View的onTouchEvent源码

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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
//1、PrePress (姑且叫为预按),这个状态是我们的view被加入到一个scrolling view中才会存在。具体的意义是,举个简单的例子,当我们将手放在listView的一item上的时候,由于当前还不知道用户是要点击item还是想滑动listview,所以先将view设为PrePress状态;

//2、Press状态,用户将手放到view上面,如果不是1的状态,就里面设置为Press状态。那么先进入了PrePress,那么将会触发一个检测,也即CheckForTap(),默认时间是100ms,如果超过了100ms,将由PrePress进入到Press状态;

//3、LongPress状态,这个状态由Press状态过度过来,如果用户在一个view上停留超过一定的时间(默认为500ms),将进入该状态。

/**
* Implement this method to handle touch screen motion events.
* <p>
* If this method is used to detect click actions, it is recommended that
* the actions be performed by implementing and calling
* {@link #performClick()}. This will ensure consistent system behavior,
* including:
* <ul>
* <li>obeying click sound preferences
* <li>dispatching OnClickListener calls
* <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
* accessibility features are enabled
* </ul>
*
* @param event The motion event.
* @return True if the event was handled, false otherwise.
*/
public boolean onTouchEvent(MotionEvent event) {

// 获取动作点击屏幕的位置坐标
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;

// 获取手势动作
final int action = event.getAction();

//获取当前 View 的 clickable 状态(是否可以点击,)
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

//如果当前View状态为DISABLED
if ((viewFlags & ENABLED_MASK) == DISABLED) {
//如果View的状态是被按压过,且当抬起事件产生,重置View状态为未按压,刷新Drawable的状态
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}

// 如果设置了触摸代理
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
//就交给mTouchDelegate.onTouchEvent处理,如果返回true,则事件被处理了,则不会向下传递
return true;
}
}

//如果当前View的clickable是可点击的,就对事件流进行细节处理
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
//如果是抬起的手势
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}

//清除各种状态
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}

//prepressed指的是,如果view包裹在一个scrolling View中,可能会进行滑动处理,所以设置了一个prePress的状态
//大致是等待一定时间,然后没有被父类拦截了事件,则认为是点击到了当前的view,从而显示点击态
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;

//如果是pressed状态或者是prepressed状态,才进行处理
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.

//如果设定了获取焦点,那么调用requestFocus获得焦点
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}

//在释放之前给用户显示View的prepressed的状态,状态需要改变为PRESSED,并且需要将背景变为按下的状态为了让用户感知到
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}

// 是否处理过长按操作了,如果是,则直接返回
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
//如果不是长按的话,仅仅是一个Tap,所以移除长按的回调
removeLongPressCallback();

// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
//UI子线程去执行click,为了让click事件开始的时候其他视觉发生变化不影响。
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}

//如果post消息失败,直接调用处理click事件
if (!post(mPerformClick)) {
performClickInternal();
}
}
}

if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}

if (prepressed) {
//ViewConfiguration.getPressedStateDuration() 获得的是按下效果显示的时间,由PRESSED_STATE_DURATION常量指定,在2.2中为125毫秒,也就是隔了125毫秒按钮的状态重置为未点击之前的状态。目的是让用户感知到click的效果
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
//如果通过post(Runnable runnable)方式调用失败,则直接调用
mUnsetPressedState.run();
}

//移除Tap的回调 重置View的状态
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;

//如果是按下的手势
case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}

//在触摸事件中执行按钮相关的动作,如果返回true则表示已经消耗了down
mHasPerformedLongPress = false;

if (!clickable) {
//仅在View支持长按时执行有效,否则直接退出方法
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
break;
}

//这个performButtonActionOnTouchDown(event) 一般的设备都是返回false.因为目前的实现中,它是处理如鼠标的右键的.(如果此View响应或者其父View响应右键菜单,那么就此事件就被消耗掉了.)
if (performButtonActionOnTouchDown(event)) {
break;
}

// Walk up the hierarchy to determine if we're inside a scrolling container.
// 判断当前view是否是在滚动器当中
boolean isInScrollingContainer = isInScrollingContainer();

// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
//将view的状态变为PREPRESSED,检测是Tap还是长按事件
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}

//如果是在滚动器当中,在滚动器当中的话延迟返回事件,延迟时间为 ViewConfiguration.getTapTimeout()=100毫秒
//在给定的tapTimeout时间之内,用户的触摸没有移动,就当作用户是想点击,而不是滑动.
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
break;

//接收到系统发出的ACTION_CANCLE事件时,重置状态, 将所有的状态设置为最初始
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;

//如果是移动的手势
case MotionEvent.ACTION_MOVE:
if (clickable) {
//将实时位置传递给背景(前景)图片
drawableHotspotChanged(x, y);
}

final int motionClassification = event.getClassification();
final boolean ambiguousGesture =
motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;
int touchSlop = mTouchSlop;
if (ambiguousGesture && hasPendingLongPressCallback()) {
final float ambiguousMultiplier =
ViewConfiguration.getAmbiguousGestureMultiplier();

// 判断当前滑动事件是否还在当前view当中,不是就执行下面的方法
if (!pointInView(x, y, touchSlop)) {
// The default action here is to cancel long press. But instead, we
// just extend the timeout here, in case the classification
// stays ambiguous.
removeLongPressCallback();
long delay = (long) (ViewConfiguration.getLongPressTimeout()
* ambiguousMultiplier);
// Subtract the time already spent
delay -= event.getEventTime() - event.getDownTime();
checkForLongClick(
delay,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
touchSlop *= ambiguousMultiplier;
}

// Be lenient about moving outside of buttons
if (!pointInView(x, y, touchSlop)) {
// Outside button
// Remove any future long press/tap checks
// 移除PREPRESSED状态和对应回调
removeTapCallback();
removeLongPressCallback();


if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
// 是PRESSED就移除长按检测,并移除PRESSED状态
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}

final boolean deepPress =
motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
if (deepPress && hasPendingLongPressCallback()) {
// process the long click action immediately
removeLongPressCallback();
checkForLongClick(
0 /* send immediately */,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
}

break;
}

return true;
}

return false;
}

① 源码虽然看起来很长,但是我们先关注 我们想要的 onClick方法 的实现。乍一看,似乎没有看到,但是我们关注代码的 114~122。也就是下面的代码

1
2
3
4
5
6
7
8
9
//UI子线程去执行click,为了让click事件开始的时候其他视觉发生变化不影响。
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}

//如果post消息失败,直接调用处理click事件
if (!post(mPerformClick)) {
performClickInternal();
}

② 看起来我们的 onClick方法 就在其中,而观察下面 PerformClick类源码 的代码。我们发现似乎关注的焦点到了 performClickInternal方法 上。

1
2
3
4
5
6
7
private final class PerformClick implements Runnable {
@Override
public void run() {
recordGestureClassification(TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP);
performClickInternal();
}
}

③ 我们进入 performClickInternal方法 的源码,发现执行了 performClick 方法。

1
2
3
4
5
6
7
8
9
private boolean performClickInternal() {
// Must notify autofill manager before performing the click actions to avoid scenarios where
// the app has a click listener that changes the state of views the autofill service might
// be interested on.
notifyAutofillManagerOnClick();

return performClick();
}

④ 那我们继续进入 performClick方法 的源码,终于看到了我们想要的东西 li.mOnClickListener.onClick(this);

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
/**
* Call this view's OnClickListener, if it is defined. Performs all normal
* actions associated with clicking: reporting accessibility event, playing
* a sound, etc.
*
* @return True there was an assigned OnClickListener that was called, false
* otherwise is returned.
*/
// NOTE: other methods on View should not call this method directly, but performClickInternal()
// instead, to guarantee that the autofill manager is notified when necessary (as subclasses
// could extend this method without calling super.performClick()).
public boolean performClick() {
// We still need to call this method to handle the cases where performClick() was called
// externally, instead of through performClickInternal()
notifyAutofillManagerOnClick();

final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}

sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

notifyEnterOrExitForAutoFillIfNeeded(true);

return result;
}

⑤ View 的事件分发似乎已经很明确了,但是还有一个问题。如果给一个控件注册了touch事件,每次点击它的时候都会触发一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP等事件。这里需要注意,如果你在执行ACTION_DOWN的时候返回了false,后面一系列其它的action就不会再得到执行了。简单的说,就是当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发后一个action。

⑥ 那么,在我们文章开始的例子中,我们在 onTouch方法 中返回了 false,ACTION_DOWN和ACTION_UP不是都得到执行了吗?

⑦ 我们简单分析一下,我们在 onTouch方法 中返回了 false,那么我们就会进入 onTouchEvent 方法中。如果 View 是可点击的,不管当前的action是什么,就一定会进入源码的 277 行,返回 true。是不是有一种被欺骗的感觉?明明在onTouch事件里返回了false,系统还是在onTouchEvent方法中帮你返回了true。就因为这个原因,才使得前面的例子中ACTION_UP可以得到执行。

⑧ 我们在我们之前的测试代码里添加一行代码(btn.setClickable(false);)就可以证明我们的说法了。打印结果也的确证实了我们的想法。

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
public class MainActivity extends AppCompatActivity {

public static final String TAG = MainActivity.class.getSimpleName();

@SuppressLint("ClickableViewAccessibility")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

Button btn = findViewById(R.id.btn);

//onTouch
btn.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d(TAG,"Button onTouch:"+event.getAction());

return false;
}
});

//onClick
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG,"Button onClick");
}
});

btn.setClickable(false);

}
}
1
2020-03-23 11:34:16.800 17146-17146/swu.xl.a03_22_touch D/MainActivity: Button onTouch:0

四. 总结

从源码中可以看出来,这两个方法都是在 View 的 dispatchTouchEvent 调用的。onTouch 优先于 OonTouchEvent 调用。如果在 onTouch方法 中返回true 就是将事件 消费(拦截)了,onTouchEvent将不会再执行。


警告问题:

上述的警告大概就是说,有可能会和点击事件发生冲突。

解决方法:使用 @SuppressLint("ClickableViewAccessibility") 注解。

参考文章

Android事件分发机制完全解析,带你从源码的角度彻底理解(上)

Android中view的dispatchTouchEvent方法源码分析

Android中view的onTouchEvent方法源码分析

Clickable - Enable - Focusable 的对比与分析

进阶必备-Android Click事件是怎么触发的?

从源码的角度分析Android中setClickable()和setEnable()的区别

setOnTouchListener报警告