Android鬼点子-View的事件分发机制


       今天来说说View的事件分发机制,其实这个问题也困扰了我很久,今天终于找了个事件,和大家一起把这里理顺!

       一个完整的事件是从手指接触到屏幕开始,手指离开屏幕结束。即以down事件开始,然后可能是若干个move事件,最后是up。

       首先,一个事件是这样传递的:Activity->Window(PhoneWindow)->View。其中View中的事件传递机制如下:ViewGroup->View->子View。如果一个子View没有处理收到的事件(onTouchEvent返回false),那么这个事件会交给它的上级处理。如果所有元素都不处理这个事件,那么最后出手的会是Activity的onTouchEvent。

       其次,点击事件的分发是由下面3个很重要的方法共同决定的。

  • public boolean dispatchTouchEvent(MotionEvent ev); 用来进行事件分发表示是否消耗当前事件。

  • public boolean onInterceptTouchEvent(MotionEvent ev); 判断是否拦截某个点击事件 返回true就拦截 false不拦截向下传递,View中没有这个方法。

  • public boolean onTouchEvent(MotionEvent ev); 用来处理点击事件。

       在ViewGroup源码的dispatchTouchEvent中有下面这样一段代码,作用是判断是ViewGroup自己处理这个事件(拦截),还是子View处理这个事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Check for interception.
final boolean intercepted;
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;
}

       intercepted这个变量是设置这个ViewGroup是否拦截。actionMasked == MotionEvent.ACTION_DOWN这句,验证了一个事件的处理是从down事件开始,如果一开始的down事件这个ViewGroup没有插手,那么接下来的move和up事件,这个ViewGroup都不会拦截。mFirstTouchTarget这个东西如果不是null,说明这个事件被它的子View成功处理了。这里指向的是它的子View。

       FLAG_DISALLOW_INTERCEPT这个标志位是在子View中设置的,一旦设置上了,这个ViewGroup就不会拦截除了down事件意外的事件,而这个标志位会在down事件时被重置。

       下面是不拦截的情况,事件会扔给它的子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
if (!canceled && !intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
final View[] children = mChildren;
final boolean customOrder = isChildrenDrawingOrderEnabled();
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder ?
getChildDrawingOrder(childrenCount, i) : i;
final View child = children[childIndex];
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
mLastTouchDownIndex = childIndex;
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}

       上面首先判断了事件的坐标是否在某个View的地盘内(是否落在子元素区域内),canViewReceivePointerEvents和isTransformedTouchPointInView来判断的。dispatchTransformedTouchEvent这个就是调用子View的dispatchTouchEvent,如果子View的dispatchTouchEvent返回true,那么这个事件就交给子View处理了,然后

1
2
3
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;

1
2
3
4
5
6
private TouchTarget addTouchTarget(View child, int pointerIdBits) {
TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}

这里就会给mFirstTouchTarget赋上值,并且跳出循环。

如果没有子View处理或者子View处理了,但是dispatchTouchEvent返回false,ViewGroup就会自己处理这个事件,这里第3个参数子View是null。

1
2
3
4
5
6
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}

       下面就是View中的处理了。先看一下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
public boolean dispatchTouchEvent(MotionEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
return true;
}
if (onTouchEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}

       明显比ViewGroup简单多了,View不会把事件继续传递下去。如果View设置了OnTouchListener,那么OnTouchListener的onTouch就会被调用,如果onTouch返回true,那么View的onTouchEvent将不会被调用。所以OnTouchListener的onTouch的优先级大于View的onTouchEvent。最后常用的onClickListener是在View的onTouchEvent中被调用的。

       最后总结一下,dispatchTouchEvent是告诉你的领导这件事是不是交给你来做,返回true,交给你;返回false,这事我不管……你去找别人。无论这件事归不归你管,领导问你能不能做的时候,你总要说句话吧?所以当一个ViewGroup或者View接受到事件时dispatchTouchEvent总会被调用。onInterceptTouchEvent是告诉你的手下的小弟刚才领导交的差事是你亲自做,还是小弟们做,你亲自做返回true,然后调用你自己的onTouchEvent,返回false,交给小弟做。

文章目录
|