Android事件分发机制

(一)基本概念

①、概述: Android里的View都是树形结构的,因此可能会出现多个View重叠的情况。当我们点击或滑动屏幕上重叠的部分的时候,这些View默认都可以响应。如果此时我们只想要某个View处理(响应/消费)这个事件,这时就可以用事件分发机制去解决这个问题。
需要说明的是,Android的事件分发机制是一种典型的责任链模式,可参考责任链模式详解

②、事件的分发、拦截、消费(响应)

流程 对应方法 Activity ViewGroup View
分发 dispatchTouchEvent 支持 支持 支持
拦截 onInterceptTouchEvent 不支持 支持 不支持
消费 onTouchEvent 支持 支持 支持

③、方法说明:
Android中触摸事件传递过程中最重要的是dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent()方法。
其中:dispatchTouchEvent 是事件分发机制中的核心,所有的事件(包括点击、长按、触摸等)调度都归它管。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 事件分发。一般不用重写。
// 执行super.dispatchTouchEvent(ev)表示事件向下分发并等待事件处理(消费)结果回传。
// 注意:如果当前控件的onInterceptTouchEvent()为true则表示事件不再往下继续传递,
// 此时交由自己onTouchEvent处理(消费)并将自己处理的结果回传给自己的dispatchTouchEvent,然后回传至上层控件。
boolean dispatchTouchEvent(MotionEvent ev) {
return super.dispatchTouchEvent(ev);
}


// 事件拦截。返回值决定了事件的传递方向。(仅判断事件是否需要拦截即可)
// 返回为false时事件会传递给子控件(默认为false 不拦截);
// 返回值为true时事件会传递给当前控件的onTouchEvent()处理(消费)
boolean onInterceptTouchEvent(MotionEvent ev);


// 事件处理(消费)。处理的结果回传给自己的dispatchTouchEvent,然后再将结果回传至上层控件。
boolean onTouchEvent(MotionEvent event);

④、Touch触摸事件:

Android中的Touch事件都是从ACTION_DOWN开始的:
单手指操作:ACTION_DOWN---ACTION_MOVE----ACTION_UP
多手指操作:ACTION_DOWN---ACTION_POINTER_DOWN---ACTION_MOVE--ACTION_POINTER_UP---ACTION_UP.

事件的调度顺序是:onTouchListener > onTouchEvent > onLongClickListener > onClickListener

需要特别指出的是:如果两个View(View1、View2)有重叠且View2(在上层)遮挡了View1的部分,则当手指点击时:
1> 仅当View1可点击时,事件将分配给View1,被View2遮挡的部分仍是View1可点击的区域。
2> 仅当View2可点击时,事件将分配给View2。
3> 当View1、View2都可以点击时,事件将分配给View2,View1收不到事件。

⑤、事件总结:

1、onTouchEvent包含了onClickListener和onLongClickListener事件。

2、View的dispatchTouchEvent主要用于调度自身的监听器和onTouchEvent。

3、给View注册OnTouchListener监听不会影响View的可点击状态,只要View是可点击的就会消费事件。

4、若给ViewGroup及其ChildView同时注册了事件监听器(如:onClick),则ChildView会被消费,ViewGroup收不到事件(无响应)。

5、如果当前正在处理的事件被上层View拦截,会收到一个ACTION_CANCEL,后续事件不会再传递过来。

6、为避免一次完整的事件(如:onClick包含ACTION_DOWN、ACTION_UP)分配给不同的View可能造成事件无法被正常消费,因此所有事件都应该被给同一个View消费。

(二)实例演示

事件分发:Activity -> RootView -> ViewGroupA -> View1
事件回传:Activity <- RootView <- ViewGroupA <- View1

MainActivity

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
/**
* @author [*昨日重现*] lhy_ycu@163.com
* @since version 1.0
*/
public class MainActivity extends AppCompatActivity{

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
DebugLog.e("MainActivity事件分发开始");
boolean result = super.dispatchTouchEvent(ev);
DebugLog.e("MainActivity事件回传结束");
return result;
}

@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result = super.onTouchEvent(event);
DebugLog.e("MainActivity事件消费---" + result);
return result;
}
}

RootView

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
/**
* @author [*昨日重现*] lhy_ycu@163.com
* @since version 1.0
*/
public class RootView extends RelativeLayout {

public RootView(Context context) {
super(context);
}

public RootView(Context context, AttributeSet attrs) {
super(context, attrs);
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
DebugLog.e("RootView事件分发");
boolean result = super.dispatchTouchEvent(ev);
DebugLog.e("RootView事件回传," + result);
return result;
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean result = super.onInterceptTouchEvent(ev);
DebugLog.e("RootView事件拦截---" + result);
return result;
}

@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result = super.onTouchEvent(event);
DebugLog.e("RootView事件消费---" + result);
return result;
}
}

ViewGroupA

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
/**
* @author [*昨日重现*] lhy_ycu@163.com
* @since version 1.0
*/
public class ViewGroupA extends RelativeLayout {
public ViewGroupA(Context context) {
super(context);
}

public ViewGroupA(Context context, AttributeSet attrs) {
super(context, attrs);
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
DebugLog.e("ViewGroupA事件分发");
boolean result = super.dispatchTouchEvent(ev);
DebugLog.e("ViewGroupA事件回传," + result);
return result;
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean result = super.onInterceptTouchEvent(ev);
DebugLog.e("ViewGroupA事件拦截---" + result);
return result;
}

@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result = super.onTouchEvent(event);
DebugLog.e("ViewGroupA事件消费---" + result);
return result;
}
}

View1

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
/**
* @author [*昨日重现*] lhy_ycu@163.com
* @since version 1.0
*/
public class View1 extends View2 {
public View1(Context context) {
super(context);
}

public View1(Context context, AttributeSet attrs) {
super(context, attrs);
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
DebugLog.e("View1事件分发");
boolean result = super.dispatchTouchEvent(ev);
DebugLog.e("View1事件回传," + result);
return result;
}

@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result = super.onTouchEvent(event);//
DebugLog.e("View1事件消费---" + result);
// switch (event.getAction()) {
// case MotionEvent.ACTION_DOWN:
// DebugLog.e("View1按下...");
// break;
// case MotionEvent.ACTION_MOVE:
// DebugLog.e("View1移动...");
// break;
// case MotionEvent.ACTION_UP:
// DebugLog.e("View1释放...");
// break;
// }
return result;
}
}

打印结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[dispatchTouchEvent:109]MainActivity事件分发开始
[dispatchTouchEvent:26]RootView事件分发
[onInterceptTouchEvent:35]RootView事件拦截---false
[dispatchTouchEvent:25]ViewGroupA事件分发
[onInterceptTouchEvent:34]ViewGroupA事件拦截---false
[dispatchTouchEvent:24]View1事件分发
[onTouchEvent:33]View1事件消费---false
[dispatchTouchEvent:26]View1事件回传,false
[onTouchEvent:41]ViewGroupA事件消费---false
[dispatchTouchEvent:27]ViewGroupA事件回传,false
[onTouchEvent:42]RootView事件消费---false
[dispatchTouchEvent:28]RootView事件回传,false
[onTouchEvent:118]MainActivity事件消费---false
[dispatchTouchEvent:111]MainActivity事件回传结束
Hawky wechat
欢迎订阅我的微信公众号
坚持原创技术分享,您的支持将鼓励我继续创作!

分享