简书链接:Android触摸事件机制从疑问到总结到分析证明并手写伪代码方便记忆
文章字数:1401,阅读全文大约需要5分钟

学习的问题

首先view触摸体系逻辑非常多,因此我这里只记录关键的信息。

疑问1:

2、 boolean onInterceptrEvent方法viewgroup有那么view有没有??
3、 down和up什么区别?

总结

1、viewgroup 的dispatchTouchEvent()默认ACTION_DOWN返回true

3、哪个viewgroup被调用了requestDisallowInterceptTouchEvent表示哪个viewgroup就不会拦截触摸事件了,那个谁的父亲也会 被设置

1
2
3
4
5
6
7
8
   final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//如果设置requestDisallowInterceptTouchEvent 为true,则表示 禁止拦截触摸事件,而且通知viewgroup以及viewgroup父容器都禁止拦截
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}

4、外部设置的onTouchListener优先级要比自身的的onTouch优先级高如果返回true就不会再调用onTouchEvent
ViewdispatchTouchEvent方法可以看到下面这句话,如果外部设置的mOnTouchListener返回true 那么就不会再触发onTouchEvent

1
2
3
4
5
6
7
8
9
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}

5、view的dispatchTouchEvent 逻辑比较少,主要处理自身的mOnTouchListener.onTouch触摸事件和自身的onTouchEvent回调
6、viewgroup的dispatchTouchEvent 逻辑比较多 ,在按下的时候如果是down 进行 onInterceptTouchEvent询问是否需要拦截,返回true表示拦截

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down so this view group continues to intercept touches.
intercepted = true;
}

7、LinearLayout等常见布局都 没有复写dispatchTouchEvent()没有为什么,因为viewgroup已经写好了。
8、默认逻辑是viewgroup的onInterceptTouchEvent在收到down事件会直接返回true,
9、在viewgroup的dispatchTouchEvent中如果 被设置禁用拦截也就是disallowIntercept 为true,那么就不会触发onInterceptTouchEventonInterceptTouchEvent方法
10、view的 onTouchEvent默认是被view的dispatchTouchEvent方法 【mOnTouchListener为空或者mOnTouchListener返回 false时候】触发,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public boolean dispatchTouchEvent(MotionEvent event) {
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}

11、onInterceptTouchEvent或者mOnTouchListener.onTouch 返回false通常代表自己不拦截这个事件了.
12、onInterceptTouchEvent一旦返回true,那么之后是就不会再调用onInterceptTouchEvent,然后也不交给子view,而是触发自己的的mOnTouchListener onTouchonTouchEvent

13、viewgroup的事件分发伪代码的摘要和view的触摸机制代码概要

viewgroup

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

public boolean dispatchTouchEvent(MotionEvent ev) {
boolean intercepted = false;
if (mDISALLOW_INTERCEIT) {
intercepted = false;
} else {

intercepted = onInterceptTouchEvent(ev);
}

if (!intercepted) {

final int childrenCount = mChildrenCount;
for (int i = childrenCount - 1; i >= 0; i--) {


preorderedList = null;//TODO 这里的意思不太理解
final boolean customOrder = preorderedList == null;// && isChildrenDrawingOrderEnabled();
int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
PseudocCodeView[] children = mChildren;
final PseudocCodeView child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
break;
}
if (dispatchTransformedTouchEvent(ev, false, child, 0)) {//调用子view的dispatchTouchEvent();
alreadyDispatchedToNewTouchTarget = true;
break;
}
}


}

if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view.
boolean canceled = true;
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
//也会调用 dispatchTransformedTouchEvent 暂时不太明白是做了什么交叉

}
return intercepted;
}



private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, PseudocCodeView child, int desiredPointerIdBits) {

final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}

MotionEvent transformedEvent = MotionEvent.obtain(event);
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {

handled = child.dispatchTouchEvent(transformedEvent);
}

// Done.
transformedEvent.recycle();
return handled;

}

view
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
  public boolean dispatchTouchEvent(MotionEvent event) {
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& ENABLE
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}

if (!result && onTouchEvent(event)) {
result = true;
}

return false;
}

protected boolean onTouchEvent(MotionEvent event) {

// if(DISABLED){reutn false;}

if (CLICKABLE || LONG_CLICKABLE || CONTEXT_CLICKABLE) {
int action = event.getAction();

switch (action) {
case MotionEvent.ACTION_UP:
boolean prepressed = true;
if (PFLAG_PRESSED || prepressed) {


if (!mHasPerformedLongPress) {
performClick();
}

}
break;

case MotionEvent.ACTION_DOWN:

break;

case MotionEvent.ACTION_CANCEL:
break;

case MotionEvent.ACTION_MOVE:
break;
}

return true;
}
return false;


}

14、requestDisallowInterceptTouchEvent方法viewgroup声明的,设置为true 会导致viewgroup不会触发自己的onInterceptTouchEvent
15、childview只有在自己所在区域才会被触发 当childview不需要的时候会传递回调parent的onTouchEvent

1
2
3
4
5
6
7
8
9
:27:13.069 14594-14594/cn.qssq666.interrview E/FrameLayoutTouch: dispatchTouchEvent start
05-02 18:27:13.069 14594-14594/cn.qssq666.interrview W/FrameLayoutTouch: onInterceptTouchEvent false
05-02 18:27:13.070 14594-14594/cn.qssq666.interrview W/TextViewTest: 你好dispatchTouchEvent start
你好onTouchEvent false
你好dispatchTouchEvent end false
05-02 18:27:13.071 14594-14594/cn.qssq666.interrview W/FrameLayoutTouch: onTouchEvent false
05-02 18:27:13.071 14594-14594/cn.qssq666.interrview E/FrameLayoutTouch: dispatchTouchEvent endfalse


上面是全false代码,执行流程是viewgroup的

完整的点击事件代码

1
2
3
4
5
6
7
8
9
10
11
12
13
05-02 18:48:42.508 25196-25196/cn.qssq666.interrview E/FrameLayoutTouch: dispatchTouchEvent start
05-02 18:48:42.508 25196-25196/cn.qssq666.interrview W/FrameLayoutTouch: onInterceptTouchEvent start
onInterceptTouchEvent end false
05-02 18:48:42.508 25196-25196/cn.qssq666.interrview W/TextViewTest: 你好dispatchTouchEvent start
05-02 18:48:42.509 25196-25196/cn.qssq666.interrview W/TextViewTest: 你好onTouchEvent start
你好post runnable android.view.View$PerformClick
你好post runnable android.view.View$UnsetPressedState //执行post
你好onTouchEvent true
你好dispatchTouchEvent end true
05-02 18:48:42.509 25196-25196/cn.qssq666.interrview E/FrameLayoutTouch: dispatchTouchEvent endtrue
05-02 18:48:42.510 25196-25196/cn.qssq666.interrview W/TextViewTest: 你好performClick
05-02 18:48:42.511 25196-25196/cn.qssq666.interrview W/MainActivity: onClick 你好

image.png

image.png
17、同级的view默认情况下不管是被同级的遮盖,都会触发事件 也就是说一个点击区域的所有子view都会响应点击事件
但是优先级是谁在上面谁先得到响应事件
image.png

所谓上面,就是xml声明最靠后的就是上面的view.

18、 dispatchTouchEvent的根源
从viewrootimp的inputState传递 ->然后传递给phonewindow里面的
decoreview的上层view dispatchPointerEvent(decorview->(appcompatActivity的windowCallbackWrap)->activity-》->PhonewWindow->dispatchTransformedTouchEvent DecorView->ViewGroup…然后各种dispatchTransformedTouchEvent

19、最后贡献我写的完整 viewgroup到view的伪代码看完伪代码整个思路绝对会清晰很多,因为不会被多余代码干扰

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
```
package cn.qssq666.interrview;

import android.support.annotation.NonNull;
import android.view.MotionEvent;
import android.view.ViewGroup;

import java.util.ArrayList;

/**
* Created by qssq on 2018/5/2 [email protected]
*/
public class PseudocCodeViewGroup extends PseudocCodeView {

/**
* requestDisallowInterceptTouchEvent =true=mDISALLOW_INTERCEIT禁止拦截
*/
private boolean mDISALLOW_INTERCEIT = false;//是否调用

ViewGroup viewGroup;
private int mChildrenCount = 10;
private TouchTarget newTouchTarget;

private PseudocCodeView[] mChildren;
private TouchTarget mFirstTouchTarget;
private boolean alreadyDispatchedToNewTouchTarget;
private ArrayList<PseudocCodeView> preorderedList;

public boolean dispatchTouchEvent(MotionEvent ev) {
boolean intercepted = false;
if (mDISALLOW_INTERCEIT) {
intercepted = false;
} else {

intercepted = onInterceptTouchEvent(ev);
}

boolean handled = false;
if (!intercepted) {

final int childrenCount = mChildrenCount;
for (int i = childrenCount - 1; i >= 0; i--) {


preorderedList = null;//TODO 这里的意思不太理解
final boolean customOrder = preorderedList == null;// && isChildrenDrawingOrderEnabled();
int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
PseudocCodeView[] children = mChildren;
final PseudocCodeView child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
break;
}
if (dispatchTransformedTouchEvent(ev, false, child, 0)) {//调用子view的dispatchTouchEvent();
alreadyDispatchedToNewTouchTarget = true;
break;
}
}


}

if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view.
boolean canceled = true;
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
TouchTarget target = mFirstTouchTarget;
TouchTarget predecessor = target;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target = next;
continue;
}
}
predecessor = target;
target = next;
}


}
return handled;
}


protected boolean onInterceptTouchEvent(MotionEvent ev) {
return ev.getAction() == MotionEvent.ACTION_DOWN;
}

/**
* 调用自己的child view
*
* @param event
* @param cancel
* @param child
* @param desiredPointerIdBits
* @return
*/

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, PseudocCodeView child, int desiredPointerIdBits) {

final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}

MotionEvent transformedEvent = MotionEvent.obtain(event);
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {

handled = child.dispatchTouchEvent(transformedEvent);
}

// Done.
transformedEvent.recycle();
return handled;

}


private TouchTarget getTouchTarget(@NonNull PseudocCodeView child) {
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
if (target.child == child) {
return target;
}
}
return null;
}

/**
* 获取以及验证预览视图,
*
* @param preorderedList
* @param children
* @param childIndex
* @return
*/

private static PseudocCodeView getAndVerifyPreorderedView(ArrayList<PseudocCodeView> preorderedList, PseudocCodeView[] children,
int childIndex) {
final PseudocCodeView child;
if (preorderedList != null) {
child = preorderedList.get(childIndex);
if (child == null) {
throw new RuntimeException("Invalid preorderedList contained null child at index "
+ childIndex);
}
} else {
child = children[childIndex];
}
return child;
}


private int getAndVerifyPreorderedIndex(int childrenCount, int i, boolean customOrder) {
final int childIndex;
if (customOrder) {
final int childIndex1 = getChildDrawingOrder(childrenCount, i);
if (childIndex1 >= childrenCount) {
throw new IndexOutOfBoundsException("getChildDrawingOrder() "
+ "returned invalid index " + childIndex1
+ " (child count is " + childrenCount + ")");
}
childIndex = childIndex1;
} else {
childIndex = i;
}
return childIndex;
}

private int getChildDrawingOrder(int childrenCount, int i) {
return 0;//省略大量代码
}

private static final class TouchTarget {
private static final int MAX_RECYCLED = 32;
private static final Object sRecycleLock = new Object[0];
private static TouchTarget sRecycleBin;
private static int sRecycledCount;
public static final int ALL_POINTER_IDS = -1; // all ones
public PseudocCodeView child;
public int pointerIdBits;
public TouchTarget next;

private TouchTarget() {
}

public static TouchTarget obtain(@NonNull PseudocCodeView child, int pointerIdBits) {
if (child == null) {
throw new IllegalArgumentException("child must be non-null");
}

final TouchTarget target;
synchronized (sRecycleLock) {
if (sRecycleBin == null) {
target = new TouchTarget();
} else {
target = sRecycleBin;
sRecycleBin = target.next;
sRecycledCount--;
target.next = null;
}
}
target.child = child;
target.pointerIdBits = pointerIdBits;
return target;
}


}
}






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
```
package cn.qssq666.interrview;

import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

import java.util.ArrayList;
import java.util.concurrent.CopyOnWriteArrayList;

/**
* Created by qssq on 2018/5/2 [email protected]
*/
public class PseudocCodeView {


private ListenerInfo mListenerInfo;
private boolean ENABLE;
private boolean result;
private boolean CLICKABLE;
private boolean LONG_CLICKABLE;
private boolean CONTEXT_CLICKABLE;
private boolean mHasPerformedLongPress;
private boolean PFLAG_PRESSED;

public boolean dispatchTouchEvent(MotionEvent event) {
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& ENABLE
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}

if (!result && onTouchEvent(event)) {
result = true;
}

return false;
}

protected boolean onTouchEvent(MotionEvent event) {

final float x = event.getX(); final float y = event.getY();
// if(DISABLED){reutn false;}

if (CLICKABLE || LONG_CLICKABLE || CONTEXT_CLICKABLE) {
int action = event.getAction();

switch (action) {
case MotionEvent.ACTION_UP:
boolean prepressed = true;
if (PFLAG_PRESSED || prepressed) {


if (!mHasPerformedLongPress) {

if (!mHasPerformedLongPress ) {
removeLongPressCallback();

}
performClick();
}


}
break;

case MotionEvent.ACTION_DOWN:

checkForLongClick(0, x, y);

break;

case MotionEvent.ACTION_CANCEL:
break;

case MotionEvent.ACTION_MOVE:
break;
}

return true;
}
return false;


}

public void performClick() {
//点击点击事件

}
CheckForLongPress mPendingCheckForLongPress=null;
public void checkForLongClick(int delayOffset,float x,float y){

if(mPendingCheckForLongPress!=null) {
mPendingCheckForLongPress = new CheckForLongPress();

}
postDelayed(mPendingCheckForLongPress,
ViewConfiguration.getLongPressTimeout() - delayOffset);
}





private final class CheckForLongPress implements Runnable {
private float mX;
private float mY;

@Override
public void run() {
if (performLongClick(mX, mY)) {
mHasPerformedLongPress = true;
}
}
}

/**
* 长按事件触发
* @param mX
* @param mY
* @return
*/
private boolean performLongClick(float mX, float mY) {
return false;
}

public void setAnchor(float x, float y) {


}



public void postDelayed(Runnable runnable,int delayTime){
//TODO
}


private void removeLongPressCallback() {
if (mPendingCheckForLongPress != null) {
removeCallbacks(mPendingCheckForLongPress);
}
}

private void removeCallbacks(Runnable mPendingCheckForLongPress) {
//TOOD
}


static class ListenerInfo {
/**
* Listener used to dispatch focus change events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected View.OnFocusChangeListener mOnFocusChangeListener;

/**
* Listeners for layout change events.
*/
private ArrayList<View.OnLayoutChangeListener> mOnLayoutChangeListeners;

protected View.OnScrollChangeListener mOnScrollChangeListener;

/**
* Listeners for attach events.
*/
private CopyOnWriteArrayList<View.OnAttachStateChangeListener> mOnAttachStateChangeListeners;

/**
* Listener used to dispatch click events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
public View.OnClickListener mOnClickListener;

/**
* Listener used to dispatch long click events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected View.OnLongClickListener mOnLongClickListener;

/**
* Listener used to dispatch context click events. This field should be made private, so it
* is hidden from the SDK.
* {@hide}
*/
protected View.OnContextClickListener mOnContextClickListener;

/**
* Listener used to build the context menu.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected View.OnCreateContextMenuListener mOnCreateContextMenuListener;

private View.OnKeyListener mOnKeyListener;

private PseudocCodeView.OnTouchListener mOnTouchListener;
// private View.OnTouchListener mOnTouchListener;

private View.OnHoverListener mOnHoverListener;

private View.OnGenericMotionListener mOnGenericMotionListener;

private View.OnDragListener mOnDragListener;

private View.OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener;

View.OnApplyWindowInsetsListener mOnApplyWindowInsetsListener;
}


public interface OnTouchListener {
/**
* Called when a touch event is dispatched to a view. This allows listeners to
* get a chance to respond before the target view.
*
* @param v The view the touch event has been dispatched to.
* @param event The MotionEvent object containing full information about
* the event.
* @return True if the listener has consumed the event, false otherwise.
*/
boolean onTouch(PseudocCodeView v, MotionEvent event);
}
}

最后

实际上代码还有很多很多,很多细节,包括嵌套滑动的兼容并没有分析,从这里的伪代码也能够让你明白,点击事件是在那个触摸方法里面触发的,是如何解决点击事件不生效的.。我是菜鸟情迁,我为自己代言