目录

Android事件分发机制

Activity事件传递

以“按下”事件为例,首先由Activity捕获点击事件,因此查看Activity.class中的dispatchTouchEvent()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// Activity.java
public boolean dispatchTouchEvent(MotionEvent ev) {
  	// 可无视:判断是否是“按下”,如果是则执行onUserInteraction(),这个方法与事件分发机制关联不大。
    if (ev.getAction() == MotionEvent.ACTION_DOWN) { 
        onUserInteraction(); 
    }
  	// 关键:事件分发,跟踪这个方法可以看到事件一层层地传递
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
  	// 处理后事:如果事件没有向下传递,那么调用这个方法直接消费事件。
    return onTouchEvent(ev);
}

事件

MotionEvent.class中所有带ACTION_的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// MotionEvent.java
// Action的掩码
public static final int ACTION_MASK             = 0xff;
// 手指按下
public static final int ACTION_DOWN             = 0;
// 手指抬起
public static final int ACTION_UP               = 1;
// 手指在屏幕上滑动
public static final int ACTION_MOVE             = 2;
// 非人为因素取消
public static final int ACTION_CANCEL           = 3;

// 还有很多其他事件,自己去看
...

基本流程:

image-20200907192127083

用户交互函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Activity.java
/**
 * Called whenever a key, touch, or trackball event is dispatched to the
 * activity.  Implement this method if you wish to know that the user has
 * interacted with the device in some way while your activity is running.
 * This callback and {@link #onUserLeaveHint} are intended to help
 * activities manage status bar notifications intelligently; specifically,
 * for helping activities determine the proper time to cancel a notification.
 *
 * <p>All calls to your activity's {@link #onUserLeaveHint} callback will
 * be accompanied by calls to {@link #onUserInteraction}.  This
 * ensures that your activity will be told of relevant user activity such
 * as pulling down the notification pane and touching an item there.
 *
 * <p>Note that this callback will be invoked for the touch down action
 * that begins a touch gesture, but may not be invoked for the touch-moved
 * and touch-up actions that follow.
 *
 * @see #onUserLeaveHint()
 */
public void onUserInteraction() {
}

根据注释中给出的信息,onUserInteraction会在按键、触摸、滑动的情况下调用。

找了ComponentActivityFragmentActivityAppCompatActivity,都没有onUserInteraction的实现,也就是说这个方法是留工程师使用的。你可以在Activity以及它的任意子类中重写它:

1
2
3
4
5
6
// MainActivity.java
class MainActivity: AppCompatActivity() {
	override fun onUserInteraction() {
		...
	}
}

事件传递

调用Window.superDispatchTouchEvent()进行事件传递。

1
getWindow().superDispatchTouchEvent(ev)

PhoneWindow

getWindow()获取了Activity.class中的mWindow变量。

1
2
3
4
// Activity.java
public Window getWindow() {
	return mWindow;
}

mWindowattach()方法中被赋值为PhoneWindow对象。所以getWindow()拿到的是PhoneWindow

1
2
3
4
// Activity.java
final void attach(...) {
	mWindow = new PhoneWindow(this, window, activityConfigCallback);
}

源码读到这里,PhoneWindow会报红,直接双击Shift按键,输入PhoneWindow进行搜索。

DecorView

PhoneWindow中的superDispatchTouchEvent()调用了mDecorsuperDispatchTouchEvent()

1
2
3
4
5
// PhoneWindow.java
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
	return mDecor.superDispatchTouchEvent(event);
}

mDecor是一个DecorView对象。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// PhoneWindow.java
private DecorView mDecor;

public PhoneWindow(...) {
	if (preservedWindow != null) {
    ...
  	mDecor = (DecorView) preservedWindow.getDecorView();
    ...
  }
}

打开DecorView

  • DecorView.superDispatchTouchEvent()调用了ViewGroup.dispatchTouchEvent()

  • DecorView继承FrameLayout,包含ViewGroup mContentRoot,所以DecorView是顶级布局。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// DecorView.java
public class DecorView extends FrameLayout ... {
  // 根内容是ViewGroup
  ViewGroup mContentRoot;
  
  // 继续调用...
  public boolean superDispatchTouchEvent(MotionEvent event) {
     return super.dispatchTouchEvent(event);
  }
}

getWindow()拿到了PhoneWindow对象,PhoneWindow中的DecorView执行事件superDispatchTouchEvent,再进一步传递给ViewGroup执行ViewGroup.dispatchTouchEvent()。再往下传递就是ViewGroup之间的事件传递了,将在后文讲解。

处理未消费的事件

1
return onTouchEvent(ev)

调用了Activity.onTouchEvent()用来处理没有在传递过程中没有被消费的事件。

1
2
3
4
5
6
7
8
9
// Activity.java
public boolean onTouchEvent(MotionEvent event) {
    if (mWindow.shouldCloseOnTouch(this, event)) {
        finish();
        return true;
    }

    return false;
}

调用了Window.shouldCloseOnTouch(),方法中判断了方法是否越界、是否按下、是否超出顶级布局,满足条件则Activity消费事件,否则丢弃事件。

1
2
3
4
5
6
7
8
// Window.java
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
      if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
              && isOutOfBounds(context, event) && peekDecorView() != null) {
          return true;
      }
      return false;
}

ViewGroup事件传递

逻辑全部在ViewGroup.dispatchTouchEvent

1
2
3
4
5
// ViewGroup.java
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
	...
}

检查

在开始正式传递事件前,进行了:一致性检查、焦点检查、隐私政策检查。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// ViewGroup.java
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    // 一致性检查:确保手势动作完整
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
    }

    // 焦点检查:确定事件的类型是焦点事件还是普通事件
    if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
        ev.setTargetAccessibilityFocus(false);
    }
    
    boolean handled = false;
    // 安全性检查:如果窗口处于隐藏状态则丢弃事件,确保不会触发隐藏窗口
    if (onFilterTouchEventForSecurity(ev)) {

一致性检查

1
2
3
4
// ViewGroup.java
if (mInputEventConsistencyVerifier != null) {
	mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}

mInputEventConsistencyVerifierView.class中,在注释中我们看到这个变量是Debugge时使用的。

1
2
3
4
5
6
7
8
// View.java
/**
 * Consistency verifier for debugging purposes.
 * @hide
 */
protected final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
            InputEventConsistencyVerifier.isInstrumentationEnabled() ?
                    new InputEventConsistencyVerifier(this, 0) : null;

Debugge模式下,肯定要启动Instrumentation(仪表)用于获取测试信息,因此isInstrumentationEnabled==true,则mInputEventConsistencyVerifier被赋值为InputEventConsistencyVerifier对象,触发InputEventConsistencyVerifier.onTouchEvent(ev, 1)来进行事件的一致性检查

在用户模式下,isInstrumentationEnabled==false,不做事件的一致性检查。在开发者模式下,isInstrumentationEnabled()==true如果ViewGroupisInstrumentationEnabled()

什么是事件的一致性检查?我们平时用手机,如果你想在屏幕上进行“单击”操作,除了按下还要抬起,才是一个完整“单击”手势,如果只是按下没有抬起,这个手势就不是“单击”手势了。事件的一致性检查就是看你有没有与“按下”配对的“抬起”操作。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// InputEventConsistencyVerifier.java
public void onTouchEvent(MotionEvent event, int nestingLevel) {
	switch (action) {
    case MotionEvent . ACTION_DOWN :
    if (mTouchEventStreamPointers != 0) {
        // 如果出现不一致就会报错
        problem("ACTION_DOWN but pointers are already down.  "
                + "Probably missing ACTION_UP from previous gesture.");
    }
    ensureHistorySizeIsZeroForThisAction(event);
    ensurePointerCountIsOneForThisAction(event);
    mTouchEventStreamPointers = 1 < < event . getPointerId (0);
    break;
	}
}

焦点检查

1
2
3
4
// ViewGroup.java
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
	ev.setTargetAccessibilityFocus(false)
}

ev.isTargetAccessibilityFocus():事件是否要交给有焦点的View处理。

isAccessibilityFocusedViewOrHost():当前ViewGroup中是否存在有焦点的View。

如果两者都满足,这说明当前ViewGroup的子视图中存在处理焦点事件的视图。那么对于当前ViewGroup,这个事件就是普通事件。将事件通过setTargetAccessibilityFocus(false)转化为普通事件进行处理。

1
2
3
4
5
6
7
8
// MotionEvent.java
/** @hide */
public final void setTargetAccessibilityFocus(boolean targetsFocus) {
	final int flags = getFlags();
  nativeSetFlags(mNativePtr, targetsFocus
          ? flags | FLAG_TARGET_ACCESSIBILITY_FOCUS
          : flags & ~FLAG_TARGET_ACCESSIBILITY_FOCUS);
}

安全性检查

1
2
3
4
// ViewGroup.java
    // 安全性检查
    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {

使用安全过滤器进行安全性检查

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// ViewGroup.java
public boolean onFilterTouchEventForSecurity(MotionEvent event) {
    if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
    && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
        // 如果当前窗口是隐藏的,那就丢弃这个事件
        return false;
    }
    // 否则消费这个事件
    return true;
}

拦截器

完成一些简单的参数赋值和状态清空后,我们要确定是否拦截。

 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

// ViewGroup.java
				// 参数赋值
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;

				// 重置状态
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }

        // 拦截器确定是否拦截
        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); 
            } else {
                intercepted = false;
            }
        } else {
            intercepted = true;
        }

参数赋值

1
2
3
4
// ViewGroup.java
        // 参数赋值
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;

将事件中的MotionEvent.action直接赋值给action变量

MotionEvent.actionMotionEvent.ACTION_MASK的并运算结果作为掩码actionMasked

重置状态

1
2
3
4
5
6
7
8
// ViewGroup.java
				// 重置状态
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // 取消和清空所有触摸目标的状态
            cancelAndClearTouchTargets(ev);
            // 重置触摸状态
            resetTouchState();
        }

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
// ViewGroup.java
				// 拦截器确定是否拦截
        /** 
         * intercepted:拦截标志位
				 * true:已拦截
				 * false:未拦截
				 */
        final boolean intercepted;
        // 默认情况下都允许拦截,如果是"按下"或者"事件曾经经过其他视图【重点,代码段后马上填坑】",则需要再进一步判断
        // 用到了参数赋值时的actionMaksed变量
				// 子视图可以通过ViewGroup.requestDisallowInterceptTouchEvent()来禁止父视图对事件的拦截
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            // 根据ViewGroup自身的信息确定是否拦截
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                // ViewGroup允许拦截,则调用拦截函数进行拦截,并为拦截标志位赋值
                intercepted = onInterceptTouchEvent(ev);
                // 将事件中的action值恢复(前面进行了位操作,要重新赋值还原)
                // 用到了参数赋值时的action变量
                ev.setAction(action); 
            } else {
                // ViewGroup不允许拦截,则不拦截,拦截标志位置false
                intercepted = false;
            }
        } else {
            // 默认情况下都允许拦截,拦截标志位置true
            intercepted = true;
        }

mFirstTouchTarget != null ,为什么意味着“事件曾经经过其他视图”?事件会从父视图向子视图往下传递,如果父视图没有消费掉视图,就会向子视图传递。在这个过程中,会有一个由TouchTarget作为结点组成的单链表,用于存储事件向下传递过程中经过的视图,每经过一个视图就在这个单链表末尾新增一个TouchTarget。例如视图层次从父到子是ABC,则单链表从头到尾是CBA,而mFirstTouchTarget就指向表头,mFirstTouchTarget != null 意味着表不为空,意味着事件曾经经过其他视图。如何得出这个过程?答案在后续的代码中。

拦截函数onInterceptTouchEvent

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public boolean onInterceptTouchEvent(MotionEvent ev) {
    if (ev.isFromSource(InputDevice.SOURCE_MOUSE) // 事件源来自可点击输入设备(屏幕)
            && ev.getAction() == MotionEvent.ACTION_DOWN // 是“按下”事件
            && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY) // 是否有屏幕点击或者按键组合
            && isOnScrollbarThumb(ev.getX(), ev.getY()) // 点击的位置是否在视图范围内
    ) {
        // 拦截
        return true;
    }
    // 不拦截
    return 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
// ViewGroup.java
        // 如果事件已拦截而且曾经经过其他视图,则将事件设定为普通事件
        if (intercepted || mFirstTouchTarget != null) {
            ev.setTargetAccessibilityFocus(false);
        }

        // 取消标志位,检查事件是不是被中途取消
        final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;

				// 变量准备
        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
        TouchTarget newTouchTarget = null; // 新的TouchTarget,用于存储新触摸目标
        boolean alreadyDispatchedToNewTouchTarget = false; // 已分发至新触摸目标标志位
				// 如果事件没被取消且没被拦截,则执行后续代码(如果有符合条件的子视图,就会将事件传递给子视图)
        if (!canceled && !intercepted) {
            View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
            ? findChildWithAccessibilityFocus() : null;

            // 对于“按下”事件...
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                // 事件的索引
                final int actionIndex = ev.getActionIndex(); 
                // 运用事件的索引来获取事件的唯一标识符
                final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) 
                : TouchTarget.ALL_POINTER_IDS;
              
                // 清理掉早前的id,以便后续赋值新的id
                removePointersFromTouchTargets(idBitsToAssign);

确定目标

如果不拦截,就找到符合条件的子视图向下传递事件;如果没有符合条件的子视图,那就“另谋出路”,代码段后会解释。

 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
// ViewGroup.java
								// 子视图数量
                final int childrenCount = mChildrenCount;
								// 如果有子视图...
                if (newTouchTarget == null && childrenCount != 0) {
                    // 事件的横坐标
                    final float x = ev.getX(actionIndex);
                    // 事件的纵坐标
                    final float y = ev.getY(actionIndex);
                    // 从前到后遍历子视图,获得的子视图列表
                    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();
                    final View[] children = mChildren;
                    // 从前到后遍历子视图
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final int childIndex = getAndVerifyPreorderedIndex(
                                childrenCount, i, customOrder);
                        final View child = getAndVerifyPreorderedView(
                                preorderedList, children, childIndex);

                        // 如果子视图无法获取焦点,则跳过本次循环
                        if (childWithAccessibilityFocus != null) {
                            if (childWithAccessibilityFocus != child) {
                                continue;
                            }
                            childWithAccessibilityFocus = null;
                            i = childrenCount - 1;
                        }

                        //如果子视图不可见,或者触摸的坐标不在子视图的范围内,则跳过本次循环
                        if (!child.canReceivePointerEvents()
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }
												
                      	// 经过上述过滤后,找到的第一个符合条件的子视图作为“新的触摸目标”,也就是事件的接收方
                        newTouchTarget = getTouchTarget(child);
                        // 如果有符合条件的视图,那就跳出整个循环
                        if (newTouchTarget != null) {
                            // 把新的id赋值给新的触摸目标
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;
                        }
												
                      	// 重置取消或抬起标志位
                        resetCancelNextUpFlag(child);
                        // 【重点】将事件传递给第一个符合条件的子视图
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            // 子视图接收“按下”事件的时间点
                            mLastTouchDownTime = ev.getDownTime();
                            if (preorderedList != null) {
                                // 从子视图列表中找到子视图的id并记录到mLastTouchDownIndex
                                for (int j = 0; j < childrenCount; j++) {
                                    if (children[childIndex] == mChildren[j]) {
                                        mLastTouchDownIndex = j;
                                        break;
                                    }
                                }
                            } else {
                                mLastTouchDownIndex = childIndex;
                            }
                            mLastTouchDownX = ev.getX();
                            mLastTouchDownY = ev.getY();
                            // 【重点】addTouchTarget,将以子视图为内容构建的新结点插入TouchTarget链表的表头
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }

                        // 将事件设置为普通事件
                        ev.setTargetAccessibilityFocus(false);
                    }
                    // 清理下内存
                    if (preorderedList != null) preorderedList.clear();
                }
								
								// 如果没有找到符合条件的子视图,将TouchTarget单链表头的元素mFirstTouchTarget赋值给新触摸目标
                if (newTouchTarget == null && mFirstTouchTarget != null) {
                    newTouchTarget = mFirstTouchTarget;
                    while (newTouchTarget.next != null) {
                        newTouchTarget = newTouchTarget.next;
                    }
                    // 重写分配id
                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                }
            }
        }

addTouchTarget的代码:

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

前面提到了mFirstTouchTarget从而引出了TouchTarget单链表的原因,正是因为addTouchTarget方法。

TouchTarget.class是以View为内容的结点。事件不断从父视图传向子视图,每传递一次,子视图收到事件时就会触发一次addTouchTarget,子视图就会被构造为新结点,以头插入的方式插入子节点,并将新插入的结点赋值给mFirstTouchTarget(因此mFirstTouchTarget就指向表头)。

如果没有找到符合条件的子视图,mFirstTouchTarget就是上一次被插入TouchTarget单链表的视图,也就是当前视图。

上面这么多代码的目标,就是为了确定mFirstTouchTarget作为传递事件的目标,而目标无非是两类:

  • 子视图
  • 当前视图

传递事件

确定mFirstTouchTarget之后,开始传递事件,如果是向子视图的传递事件,那已经在上述代码中完成了,下面是当前视图的传递事件。

 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
// ViewGroup.java
        if (mFirstTouchTarget == null) {
            // 【重点】如果没有触摸目标,直接由当前视图进行处理
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
            // 如果有触摸目标,那么ACTION_MOVE、ACTION_UP等事件开始相继执行。
            TouchTarget predecessor = null;
            TouchTarget target = mFirstTouchTarget;
            while (target != null) {
                final TouchTarget next = target.next;
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                                    target.child, target.pointerIdBits)) {
                        handled = true;
                    }
                    if (cancelChild) {
                        if (predecessor == null) {
                            mFirstTouchTarget = next;
                        } else {
                            predecessor.next = next;
                        }
                        target.recycle();
                        target = next;
                        continue;
                    }
                }
                predecessor = target;
                target = next;
            }
        }

        // 发生抬起或非人为因素取消时,更新触摸目标
        if (canceled
                || actionMasked == MotionEvent.ACTION_UP
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            resetTouchState();
        } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
            final int actionIndex = ev.getActionIndex();
            final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
            removePointersFromTouchTargets(idBitsToRemove);
        }
    }

    if (!handled && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
    }
    return handled;
}

传递事件过程的主要函数是dispatchTransformedTouchEvent(),注意其中的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
// ViewGroup.class
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
    final boolean handled;

    final int oldAction = event.getAction();
    if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
        event.setAction(MotionEvent.ACTION_CANCEL);
        if (child == null) {
            // 如果传入的子视图为null,就调用当前视图的dispatchTouchEvent(),相当于分发给当前视图。
            handled = super.dispatchTouchEvent(event);
        } else {
            // 如果传入的子视图不为null,则执行子视图的dispatchTouchEvent(),相当于分发给子视图。
            handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        return handled;
    }

    ...
    
    return handled;
}

注意,这里调用的不是ViewGroup.dispatchTouchEvent()而是View.dispatchTouchEvent()。从个方法开始进入View的事件传递。

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
// View.class
public boolean dispatchTouchEvent(MotionEvent event) {
   // 焦点事件的处事方式
    if (event.isTargetAccessibilityFocus()) {
        if (!isAccessibilityFocusedViewOrHost()) {
            return false;
        }
        // 处理完后转为普通事件
        event.setTargetAccessibilityFocus(false);
    }

    boolean result = false;
		// 一致性检查
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(event, 0);
    }

    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // 如果存在滚动操作,立刻停止
        stopNestedScroll();
    }

    // 安全性检查
    if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        ListenerInfo li = mListenerInfo;
        // 先执行OnTouchListener.onTouch()消费事件
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
        && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
        // 如果OnTouchListener.onTouch()消费事件没有消费事件,再执行onTouchEvent()消费事件
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }

    if (!result && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }

    if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        stopNestedScroll();
    }

    return result;
}

View.onTouchEvent()代码,大致逻辑:如果视图可以被点击或者长按并且在特定手势下满足一些特定条件,则返回true,表示已经被消费;否则返回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
 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
public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;
    final int action = event.getAction();

    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
    || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

    
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        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)) {
            return true;
        }
    }

    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;
            }
            boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
            if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
            // take focus if we don't have it already and we should in
            // touch mode.
            boolean focusTaken = false;
            if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                focusTaken = requestFocus();
            }

            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
                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.
                    if (mPerformClick == null) {
                        mPerformClick = new PerformClick();
                    }
                    if (!post(mPerformClick)) {
                        performClickInternal();
                    }
                }
            }

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

            if (prepressed) {
                postDelayed(mUnsetPressedState,
                        ViewConfiguration.getPressedStateDuration());
            } else if (!post(mUnsetPressedState)) {
                // If the post failed, unpress right now
                mUnsetPressedState.run();
            }

            removeTapCallback();
        }
            mIgnoreNextUpEvent = false;
            break;

            case MotionEvent.ACTION_DOWN:
            if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
                mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
            }
            mHasPerformedLongPress = false;

            if (!clickable) {
                checkForLongClick(
                        ViewConfiguration.getLongPressTimeout(),
                        x,
                        y,
                        TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                break;
            }

            if (performButtonActionOnTouchDown(event)) {
                break;
            }

            // Walk up the hierarchy to determine if we're inside a scrolling container.
            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;
                if (mPendingCheckForTap == null) {
                    mPendingCheckForTap = new CheckForTap();
                }
                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;

            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();
                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
                removeTapCallback();
                removeLongPressCallback();
                if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                    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;
}

总结

事件分发顺序:Activity=>ViewGroup=>View

Activity事件传递机制:Activity通过PhoneWindow将事件传递给顶级布局DecorView,顶级布局将事件传给它内含的ViewGroup,调用ViewGroupViewGroup.dispatchTouchEvent()来分发事件。

ViewGroup事件传递机制:ViewGroup.dispatchTouchEvent()中先通过ViewGroup.onInterceptTouchEvent()判断是否拦截,如果拦截则将事件传递给子视图,不拦截则将事件传递给当前视图,从而确定触摸目标mFirstTouchTarget是子视图还是当前视图。再调用ViewGroup.dispatchTransformedTouchEvent()将事件分发给mFirstTouchTarget,当前视图和子视图都通过调用View.dispatchTouchEvent()来消费事件。

View事件传递机制:View.dispatchTouchEvent()中通过调用OnTouchListener.onTouch()消费事件,若成功消费则返回true,否则调用View.onTouchEvent()事件并返回false,并且层层返回false,最后由Activity.onTouchEvent()来消费事件。

image-20200907171148737