Android事件分发机制详解(一)
目录
/%E7%9B%AE%E5%BD%95.png)
1. 基础认知
1.1 事件分发的”事件“是指什么?
答:点击事件(Touch事件)。具体介绍如下:
/%E7%82%B9%E5%87%BB%E4%BA%8B%E4%BB%B6%E5%AE%9A%E4%B9%89.png)
此处需要特别说明:事件列,即指从手指接触屏幕至手指离开屏幕这个过程产生的一系列事件。一般情况下,事件列都是以DOWN事件开始、UP事件结束,中间有无数的MOVE事件。

1.2 事件分发的本质
答:将点击事件(MotionEvent)传递到某个具体的View & 处理的整个过程
即 事件传递的过程 = 分发过程。
1.3 事件在哪些对象之间进行传递?
答:Activity、ViewGroup、View。Android的UI界面由Activity、ViewGroup、View 及其派生类组成


1.4 事件分发的顺序
即 事件传递的顺序:Activity -> ViewGroup -> View
即:1个点击事件发生后,事件先传到Activity、再传到ViewGroup、最终再传到 View

1.5 事件分发过程由哪些方法协作完成?
答:dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()

下文会对这3个方法进行详细介绍
1.6 总结

- 至此,相信大家已经对
Android的事件分发有了感性的认知
- 下面,我将详细介绍
Android事件分发机制
2. 事件分发机制流程概述
Android事件分发流程 = Activity -> ViewGroup -> View
即:1个点击事件发生后,事件先传到Activity、再传到ViewGroup、最终再传到 `View
即要想充分理解Android分发机制,本质上是要理解:
Activity对点击事件的分发机制
ViewGroup对点击事件的分发机制
View对点击事件的分发机制
下面,我将通过源码,全面解析 事件分发机制
即按顺序讲解:Activity事件分发机制、ViewGroup事件分发机制、View事件分发机制
3. 事件分发机制流程详细分析
主要包括:Activity事件分发机制、ViewGroup事件分发机制、View事件分发机制
流程1:Activity的事件分发机制
Android事件分发机制首先会将点击事件传递到Activity中,具体是执行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 83 84
|
public boolean dispatchTouchEvent(MotionEvent ev) {
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
} return onTouchEvent(ev); }
@Override public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event); }
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) { finish(); return true; } return false; }
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(context, event) && peekDecorView() != null) {
return true; }
return false; }
|
源码总结
当一个点击事件发生时,从Activity的事件分发开始(Activity.dispatchTouchEvent()),流程总结如下:

核心方法总结
主要包括:dispatchTouchEvent()、onTouchEvent() 总结如下

那么,ViewGroup的dispatchTouchEvent()什么时候返回true / false?请继续往下看ViewGroup事件的分发机制
流程2: ViewGroup的事件分发机制
从上面Activity的事件分发机制可知,在Activity.dispatchTouchEvent()实现了将事件从Activity->ViewGroup的传递,ViewGroup的事件分发机制从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
|
public boolean dispatchTouchEvent(MotionEvent ev) {
...
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
for (int i = count - 1; i >= 0; i--) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { child.getHitRect(frame);
if (frame.contains(scrolledXInt, scrolledYInt)) { final float xc = scrolledXFloat - child.mLeft; final float yc = scrolledYFloat - child.mTop; ev.setLocation(xc, yc); child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
if (child.dispatchTouchEvent(ev)) {
mMotionTarget = child; return true; } } } } } }
...
return super.dispatchTouchEvent(ev);
...
}
public boolean onInterceptTouchEvent(MotionEvent ev) { return false;
}
|
源码总结
Android事件分发传递到Acitivity后,总是先传递到ViewGroup、再传递到View。流程总结如下:(假设已经经过了Acitivity事件分发传递并传递到ViewGroup)

核心方法总结
主要包括:dispatchTouchEvent()、onTouchEvent() 、onInterceptTouchEvent()总结如下

实例分析
1. 布局说明

2. 测试代码
布局文件:activity_main.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/my_layout" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto" android:focusableInTouchMode="true" android:orientation="vertical">
<Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="按钮1" />
<Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="按钮2" />
</LinearLayout>
|
核心代码:MainActivity.java
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
| public class MainActivity extends AppCompatActivity {
Button button1,button2; ViewGroup myLayout;
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
button1 = (Button)findViewById(R.id.button1); button2 = (Button)findViewById(R.id.button2); myLayout = (LinearLayout)findViewById(R.id.my_layout);
myLayout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.d("TAG", "点击了ViewGroup"); } });
button1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.d("TAG", "点击了button1"); } });
button2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.d("TAG", "点击了button2"); } }); } }
|
3. 测试结果
1 2 3 4 5 6 7 8
| 点击了button1
点击了button2
点击了ViewGroup
|
4. 结果分析
- 点击Button时,因为ViewGroup默认不拦截,所以事件会传递到子View Button,于是执行Button.onClick()。
- 此时ViewGroup. dispatchTouchEvent()会直接返回true,所以ViewGroup自身不会处理该事件,于是ViewGroupLayout的dispatchTouchEvent()不会执行,所以注册的onTouch()不会执行,即onTouchEvent() -> performClick() -> onClick()整个链路都不会执行,所以最后不会执行ViewGroup设置的onClick()里。
- 点击空白区域时,ViewGroup. dispatchTouchEvent()里遍历所有子View希望找到被点击子View时找不到,所以ViewGroup自身会处理该事件,于是执行onTouchEvent() -> performClick() -> onClick(),最终执行ViewGroupLayout的设置的onClick()
流程3:View的事件分发机制
从上面ViewGroup事件分发机制知道,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 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
|
public boolean dispatchTouchEvent(MotionEvent event) {
if ( (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener != null && mOnTouchListener.onTouch(this, event)) { return true; }
return onTouchEvent(event); }
public void setOnTouchListener(OnTouchListener l) {
mOnTouchListener = l;
}
button.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return false; } });
public boolean onTouchEvent(MotionEvent event) {
...
if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP: performClick(); break;
case MotionEvent.ACTION_DOWN: postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); break;
case MotionEvent.ACTION_CANCEL: refreshDrawableState(); removeTapCallback(); break;
case MotionEvent.ACTION_MOVE: final int x = (int) event.getX(); final int y = (int) event.getY();
int slop = mTouchSlop; if ((x < 0 - slop) || (x >= getWidth() + slop) || (y < 0 - slop) || (y >= getHeight() + slop)) { removeTapCallback(); if ((mPrivateFlags & PRESSED) != 0) { removeLongPressCallback(); mPrivateFlags &= ~PRESSED; refreshDrawableState(); } } break; }
return true; } return false; }
public boolean performClick() {
if (mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); mOnClickListener.onClick(this); return true; } return false; }
|
源码总结

这里需要特别注意的是,onTouch()的执行 先于 onClick()
核心方法总结
主要包括:dispatchTouchEvent()、onTouchEvent()

实例分析
在本示例中,将分析两种情况:
- 注册Touch事件监听 且 在onTouch()返回false
- 注册Touch事件监听 且 在onTouch()返回true
分析1:注册Touch事件监听 且 在onTouch()返回false
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| button.setOnTouchListener(new View.OnTouchListener() {
@Override public boolean onTouch(View v, MotionEvent event) { System.out.println("执行了onTouch(), 动作是:" + event.getAction()); return false; } });
button.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) { System.out.println("执行了onClick()"); } });
|
测试结果
1 2 3
| 执行了onTouch(), 动作是:0 执行了onTouch(), 动作是:1 执行了onClick()
|
测试结果说明
- 点击按钮会产生两个类型的事件-按下View与抬起View,所以会回调两次onTouch();
- 因为onTouch()返回了false,所以事件无被消费,会继续往下传递,即调用View.onTouchEvent();
- 调用View.onTouchEvent()时,对于抬起View事件,在调用performClick()时,因为设置了点击事件,所以会回调onClick()。
分析2:注册Touch事件监听 且 在onTouch()返回true
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| button.setOnTouchListener(new View.OnTouchListener() {
@Override public boolean onTouch(View v, MotionEvent event) { System.out.println("执行了onTouch(), 动作是:" + event.getAction()); return true; } });
button.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) { System.out.println("执行了onClick()"); } });
|
测试结果
1 2
| 执行了onTouch(), 动作是:0 执行了onTouch(), 动作是:1
|
测试结果说明
- 点击按钮会产生两个类型的事件-按下View与抬起View,所以会回调两次onTouch();
- 因为onTouch()返回了true,所以事件被消费,不会继续往下传递,View.dispatchTouchEvent()直接返回true;
- 所以最终不会调用View.onTouchEvent(),也不会调用onClick()。
至此,关于Android事件分发机制的内容已经讲解完毕,即Activity、ViewGroup、View的事件分发机制。
即:Activity、ViewGroup、View 的事件分发机制