Android事件分发机制

摘要

讲解View和ViewGroup的事件传递 & Activity的事件来源。

View和ViewGroup的事件传递:部分转载自图解Android事件分发机制,但稍作修改。

Activity的事件来源:仿照Android中MotionEvent的来源和ViewRootImpl写了一个简单的例子验证。

0.事件来源和ViewRootImpl的事件传递

我们知道View的事件分发顺序为Activity——>PhoneWindow——>DecorView,那么Activity的事件来源在哪里呢?

下面这张图就是事件传递的一个顺序图:

事件分发传递流程图

当一个InputEvent到来时,ViewRootImpl会寻找合适它的InputStage来处理。图中以ViewPostImeInputStage为例,事件被ViewPostImeInputStage分发到View,这里的View事在ViewRootImpl#setView()方法中被赋值的,mView就是DecorView,所以DecorView#dispatchTouchEvent()被调用,在Activity#attach()方法中Window.Callback被赋值,所以DecorView#dispatchTouchEvent()中的getCallback()指代的是Activity,最终调用Activity#dispatchTouchEvent()。

仿照Android中MotionEvent的来源和ViewRootImpl写了一个简单的例子验证。

准备工作:
MainActivity代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MainActivity extends Activity {
private static final String TAG = MainActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test_touch_activity);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(TAG, "dispatchTouchEvent---" + ev.getAction());
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG, "onTouchEvent---" + event.getAction());
return super.onTouchEvent(event);
}
}

R.layout.test_touch_activity代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.xiongcen.testtouch.widget.ViewGroup1
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="20dp"
android:background="#222eee"
android:orientation="vertical">
</com.xiongcen.testtouch.widget.ViewGroup1>
</LinearLayout>

ViewGroup1的代码如下:

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
public class ViewGroup1 extends LinearLayout {
private static final String TAG = ViewGroup1.class.getSimpleName();
public ViewGroup1(Context context) {
super(context);
}
public ViewGroup1(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ViewGroup1(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(TAG, "dispatchTouchEvent---" + ev.getAction());
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.i(TAG, "onInterceptTouchEvent---" + ev.getAction());
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG, "onTouchEvent---" + event.getAction());
Thread.dumpStack();
return true;
}
}

在ViewGroup1#onTouchEvent()中调用Thread.dumpStack();并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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
01-08 10:42:14.865 18600-18600/com.examples.customtouch I/MainActivity: dispatchTouchEvent---0
01-08 10:42:14.865 18600-18600/com.examples.customtouch I/ViewGroup1: dispatchTouchEvent---0
01-08 10:42:14.865 18600-18600/com.examples.customtouch I/ViewGroup1: onInterceptTouchEvent---0
01-08 10:42:14.865 18600-18600/com.examples.customtouch I/ViewGroup1: onTouchEvent---0
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: java.lang.Throwable: stack dump
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at java.lang.Thread.dumpStack(Thread.java:496)
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at com.xiongcen.testtouch.widget.ViewGroup1.onTouchEvent(ViewGroup1.java:44)
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at android.view.View.dispatchTouchEvent(View.java:7246)
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2168)
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1903)
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at com.xiongcen.testtouch.widget.ViewGroup1.dispatchTouchEvent(ViewGroup1.java:32)
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2174)
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1875)
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2174)
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1875)
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2174)
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1875)
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2174)
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1875)
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.java:1953)
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at com.android.internal.policy.impl.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1405)
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at android.app.Activity.dispatchTouchEvent(Activity.java:2410)
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at com.xiongcen.testtouch.MainActivity.dispatchTouchEvent(MainActivity.java:27)
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:1901)
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at android.view.View.dispatchPointerEvent(View.java:7426)
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at android.view.ViewRootImpl.deliverPointerEvent(ViewRootImpl.java:3220)
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:3165)
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:4292)
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:4271)
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:4363)
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:179)
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at android.os.MessageQueue.nativePollOnce(Native Method)
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at android.os.MessageQueue.next(MessageQueue.java:125)
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at android.os.Looper.loop(Looper.java:124)
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at android.app.ActivityThread.main(ActivityThread.java:5041)
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at java.lang.reflect.Method.invokeNative(Native Method)
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at java.lang.reflect.Method.invoke(Method.java:511)
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
01-08 10:42:14.865 18600-18600/com.examples.customtouch W/System.err: at dalvik.system.NativeStart.main(Native Method)

可以看到堆栈的打印信息和前面的事件分发传递流程图分析的一致。

1.View和ViewGroup的事件传递

1.1 针对ACTION_DOWN事件的事件走向图

1.1.1 如果事件不被中断,整个事件流向是一个U型图。

如果我们没有对控件里面的方法进行重写或更改返回值,而直接用super调用父类的默认实现,那么整个事件流向应该是从Activity—->ViewGroup—>View,从上往下调用dispatchTouchEvent方法,一直到叶子节点(View)的时候,再由View—>ViewGroup—>Activity从下往上调用onTouchEvent方法。

1.1.2 dispatchTouchEvent方法和onTouchEvent方法一旦return true,事件就停止传递了(到达终点),也就是事件被消费了。

1.1.3 dispatchTouchEvent方法和onTouchEvent方法,return false的时候事件都回传给父控件的onTouchEvent处理。

  1. 对于dispatchTouchEvent方法return false的含义是:事件停止往子View传递和分发同时开始往父控件回溯(父控件的onTouchEvent方法开始从下往上回传直到某个onTouchEvent方法return true),事件分发机制就像递归,return false的意义就是递归停止然后开始回溯。

  2. 对于onTouchEvent方法return false的含义是:不消费事件,并让事件继续往父控件的方向从下往上流动。

1.1.4 ViewGroup和View的dispatchTouchEvent、onTouchEvent、onInterceptTouchEvent方法的默认实现就是会让整个事件安装U型完整走完。

1.1.5 onInterceptTouchEvent方法中return true就会交给自己的onTouchEvent的处理,如果不拦截就是继续往子控件往下传。

默认是不会去拦截的,因为子View也需要这个事件,所以onInterceptTouchEvent拦截器return super.onInterceptTouchEvent()和return false是一样的,是不会拦截的,事件会继续往子View的dispatchTouchEvent传递。

1.1.6 ViewGroup和View的dispatchTouchEvent方法返回super.dispatchTouchEvent()的时候事件流走向。

1.1.6.1 ViewGroup的dispatchTouchEvent方法return true是终结传递。return false是回溯到父View的onTouchEvent方法。

问题:ViewGroup怎样通过dispatchTouchEvent方法把事件分发到自己的onTouchEvent处理?

回答:return true和false都不行,那么只能通过Interceptor把事件拦截下来给自己的onTouchEvent,dispatchTouchEvent方法return super.dispatchTouchEvent(),在onInterceptTouchEvent方法return true。所以ViewGroup的dispatchTouchEvent方法的super默认实现就是去调用onInterceptTouchEvent方法。

1.1.6.2 View的dispatchTouchEvent方法return true是终结传递。return false是回溯到父View的onTouchEvent方法。

问题:View怎样把事件分发给自己的onTouchEvent处理?

回答:return super.dispatchTouchEvent()。View类的dispatchTouchEvent方法默认实现就是能帮你调用View自己的onTouchEvent方法。

1.1.7 结论

——->后面代表事件目标需要怎么做。

1.1.7.1 ViewGroup和View的dispatchTouchEvent方法
  1. 自己消费,终结传递。——->return true。

  2. View给自己的onTouchEvent处理。——->调用super.dispatchTouchEvent()默认调用View自己的onTouchEvent()。

  3. ViewGroup给自己的onTouchEvent处理。——->调用super.dispatchTouchEvent()默认调用onInterceptTouchEvent(),在onInterceptTouchEvent方法return true就会把事件分给自己的onTouchEvent处理。

  4. ViewGroup传给子View。——->调用super.dispatchTouchEvent()默认调用onInterceptTouchEvent(),在onInterceptTouchEvent方法return false就会把事件传给子类。

  5. 不传给子View,事件终止往下传递,事件开始回溯(从父View的onTouchEvent开始事件从下往上回归执行每个控件的onTouchEvent)。——->return false。

1.1.7.2 ViewGroup和View的onTouchEvent方法
  1. 自己消费,终结传递。——->return true。

  2. 继续从下往上传,不消费事件,让父View也可以收到这个事件。——->return false;View的默认实现是不消费,所以super==false。

1.1.7.3 ViewGroup的onInterceptTouchEvent方法
  1. 拦截下来,给自己的onTouchEvent处理。——->return true。

  2. 不拦截,把事件往下传给子View。——->return false;ViewGroup默认是不拦截的,所以super==false。

1.2 针对ACTION_MOVE和ACTION_UP事件的事件走向图

通过几张图看看不同场景下,ACTION_MOVE事件和ACTION_UP事件的具体走向并总结规律。

下图中的布局分布见R.layout.test_touch_activity.xml,也可以见Demo

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
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.xiongcen.testtouch.widget.ViewGroup1
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#222eee"
android:orientation="vertical">
<com.xiongcen.testtouch.widget.ViewGroup2
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#ffffff"
android:orientation="vertical">
<com.xiongcen.testtouch.widget.View2
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="View2" />
</com.xiongcen.testtouch.widget.ViewGroup2>
</com.xiongcen.testtouch.widget.ViewGroup1>
</LinearLayout>

1.2.1 dispatchTouchEvent方法相关

1.2.1.1 在ViewGroup1的dispatchTouchEvent方法return true消费这次事件,触摸View

1.2.1.2 在ViewGroup2的dispatchTouchEvent方法return true消费这次事件,触摸View

1.2.1.3 在View的dispatchTouchEvent方法return true消费这次事件,触摸View

1.2.1.4 结论1

某个控件的dispatchTouchEvent方法return true消费事件,终结传递,那么收到ACTION_DOWN的函数也能收到ACTION_MOVE和ACTION_UP。

1.2.2 onTouchEvent方法相关

1.2.2.1 在View的onTouchEvent方法return true消费这次事件,触摸View

注意:条件ACTION_DOWN return true,ACTION_MOVE/ACTION_UP return super,ACTION_MOVE/ACTION_UP的事件传递不回溯到ViewGroup,直接到顶级Activity。

#### 1.2.2.2 在ViewGroup2的onTouchEvent方法return true消费这次事件,触摸View



#### 1.2.2.3 在ViewGroup1的onTouchEvent方法return true消费这次事件,触摸View



#### 1.2.2.4 在Activity的onTouchEvent方法return true消费这次事件,触摸View



#### 1.2.2.5 结论2

在哪个View的onTouchEvent方法return true,那么ACTION_MOVE和ACTION_UP的事件从上往下传到这个View后就不再往下传递了,而直接传给自己的onTouchEvent并结束本次事件传递过程。

1.2.3 混合

1.2.3.1 在View的dispatchTouchEvent方法return false并且Activity的onTouchEvent方法return true消费这次事件,触摸View

1.2.3.2 在View的dispatchTouchEvent方法return false并且ViewGroup1的onTouchEvent方法return true消费这次事件,触摸View

1.2.3.3 在View的dispatchTouchEvent方法return false并且ViewGroup2的onTouchEvent方法return true消费这次事件,触摸View

1.2.3.4 在ViewGroup2的dispatchTouchEvent方法return false并且ViewGroup1的onTouchEvent方法return true消费这次事件,触摸View

1.2.3.5 在ViewGroup2的onInterceptTouchEvent方法return true拦截此次事件并且在ViewGroup1的onTouchEvent方法return true消费这次事件,触摸View

以上验证结果见View和ViewGroup的事件传递[验证结果].txt

2.ViewGroup的dispatchTouchEvent方法的详细分析

规律总结出来了,但是代码执行的流程又是怎么样的呢?

先看一个test case 4.3,见View和ViewGroup的事件传递[验证结果].txt,我们将
ViewGroup1#dispatchTouchEvent() return super.dispatchTouchEvent();

ViewGroup1#onInterceptTouchEvent() return true;

表示拦截事件传递交给自己的onTouchEvent()处理,触摸View2

结果如下:

1
2
3
4
5
6
7
8
9
01-08 19:21:43.996 22072-22072/com.examples.customtouch I/MainActivity: dispatchTouchEvent---0
01-08 19:21:43.996 22072-22072/com.examples.customtouch I/ViewGroup1: dispatchTouchEvent---0
01-08 19:21:43.996 22072-22072/com.examples.customtouch I/ViewGroup1: onInterceptTouchEvent---0
01-08 19:21:43.996 22072-22072/com.examples.customtouch I/ViewGroup1: onTouchEvent---0
01-08 19:21:43.996 22072-22072/com.examples.customtouch I/MainActivity: onTouchEvent---0
01-08 19:21:44.252 22072-22072/com.examples.customtouch I/MainActivity: dispatchTouchEvent---2
01-08 19:21:44.252 22072-22072/com.examples.customtouch I/MainActivity: onTouchEvent---2
01-08 19:21:44.748 22072-22072/com.examples.customtouch I/MainActivity: dispatchTouchEvent---1
01-08 19:21:44.748 22072-22072/com.examples.customtouch I/MainActivity: onTouchEvent---1

看到ACTION_MOVE和ACTION_UP的事件只有MainActivity响应,感到百思不得其解,最终debug调试才真正明白。见github提交注释

注释内容参考文章Android Deeper(00) - Touch事件分发响应机制

以下是流程图,来源文章Android Deeper(00) - Touch事件分发响应机制

其中最最重要的是对mFirstTouchTarget这个变量的赋值。

在test case 4.3中,由于ACTION_DOWN事件没有任何dispatchTouchEvent方法和onTouchEvent方法消费事件,所以导致DecorView、FrameLayout、LinearLayout、ViewGroup1、ViewGroup2的mFirstTouchTarget均为null。

前面我们分析了View的事件分发顺序为Activity——>PhoneWindow——>DecorView,记住这一点,我们继续分析DecorView的ACTION_MOVE和ACTION_UP事件:

  1. 根据2213行的if判断条件,因为action既不是ACTION_DOWN && mFirstTouchTarget==null,所以intercept变量被赋值为true,调用不到ViewGroup的onInterceptTouchEvent()方法。

  2. 步骤1导致2250行的if判断条件,也无法走进。

  3. 直接走到2365行,由于mFirstTouchTarget==null,执行dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
    方法,由于child==null,最终调用super.dispatchTouchEvent(event);即View.dispatchTouchEvent()方法,最终调用View.onTouchEvent()方法,因为没有哪个方法消费该事件所以最终2344行的handle变量的赋值结果为false。

  4. 因此在DecorView#superDispatchTouchEvent()方法中return false,导致PhoneWindow#superDispatchTouchEvent()方法中也return false,最终导致Activity中的代码中第2个if条件getWindow()#superDispatchTouchEvent()方法rerun false,最终调用Activity#onTouchEvent()方法。也就验证了test case 4.3的结果,ACTION_MOVE和ACTION_UP的事件只会在我们重写的MainActivity中dispatchTouchEvent()和onTouchEvent()两个方法中打印。由于只执行到DecorView相关的方法,所以它的子类的打印均不会被调用。

1
2
3
4
5
6
7
8
9
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}

参考

  1. Android中MotionEvent的来源和ViewRootImpl

  2. Android中的ViewRootImpl类源码解析

  3. 图解Android事件分发机制

  4. Android Deeper(00) - Touch事件分发响应机制