概述
Android事件分发也属于Android的基础,我尝试用自己的话来叙述一下,结果说不出来…能理解,但是不能说,只能说是不太熟悉。还是写一篇文章来记录一下吧。
使用场景
在View与View之间嵌套的时候,通常要处理事件的分发,判断是否拦截或是下发一个事件。
事件作用于View,分发的对象是对触摸屏幕产生的事件,让该事件在具体View上产生作用。
所以,分发的对象是:点击事件。作用的对象是:Activity,ViewGroup,View。
分发的顺序是:Activity ——> ViewGroup ——> View
事件类型
MotionEvent.ACTION_DOWN 按下View(所有事件的开始)
MotionEvent.ACTION_UP 抬起View(与DOWN对应)
MotionEvent.ACTION_MOVE 滑动View
MotionEvent.ACTION_CANCEL 结束事件(非人为原因)
方法
- dispatchTouchEvent:
用来进行事件的分发。
如果事件能够被传递给当前View,那么此方法一定会被调用,返回结果受到当前View的onTouchEvent和下级View的dispatchTouchEvent方法影响,表示是否消耗当前事件。 - onInterceptTouchEvent:
会在dispatchTouchEvent方法内部调用,用来判断是否拦截某个事件,如果当前View拦截了某个事件(返回true),那么在同一个事件序列当中,此方法不会再次被调用,返回结果表示是否拦截当前事件。 - onTouchEvent:
在dispatchTouchEvent方法中调用,用来处理事件,返回结果表示是否消耗当前事件,如果不消耗(返回false),则在同一个事件序列中,当前View无法再次接收到后续的事件队列
onTouchEvent默认的返回值由clickable和longClickable共同决定,只要有一个为true,onTouchEvent的返回值就是true,longClickable的默认值都为false,clickable的默认值分情况,Button为true,TextView为false。
Activity中的事件分发
传递点击事件,当事件能够传递给当前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
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/**
* 源码分析:Activity.dispatchTouchEvent()
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
// 一般事件列开始都是DOWN事件 = 按下事件,故此处基本是true
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
// ->>分析1
}
// ->>分析2
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
// 若getWindow().superDispatchTouchEvent(ev)的返回true
// 则Activity.dispatchTouchEvent()就返回true,则方法结束。即 :该点击事件停止往下传递 & 事件传递过程结束
// 否则:继续往下调用Activity.onTouchEvent
}
// ->>分析4
return onTouchEvent(ev);
}
/**
* 分析1:onUserInteraction()
* 作用:实现屏保功能
* 注:
* a. 该方法为空方法
* b. 当此activity在栈顶时,触屏点击按home,back,menu键等都会触发此方法
*/
public void onUserInteraction() {
}
// 回到最初的调用原处
/**
* 分析2:getWindow().superDispatchTouchEvent(ev)
* 说明:
* a. getWindow() = 获取Window类的对象
* b. Window类是抽象类,其唯一实现类 = PhoneWindow类;即此处的Window类对象 = PhoneWindow类对象
* c. Window类的superDispatchTouchEvent() = 1个抽象方法,由子类PhoneWindow类实现
*/
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
// mDecor = 顶层View(DecorView)的实例对象
// ->> 分析3
}
/**
* 分析3:mDecor.superDispatchTouchEvent(event)
* 定义:属于顶层View(DecorView)
* 说明:
* a. DecorView类是PhoneWindow类的一个内部类
* b. DecorView继承自FrameLayout,是所有界面的父类
* c. FrameLayout是ViewGroup的子类,故DecorView的间接父类 = ViewGroup
*/
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
// 调用父类的方法 = ViewGroup的dispatchTouchEvent()
// 即 将事件传递到ViewGroup去处理,详细请看ViewGroup的事件分发机制
}
// 回到最初的调用原处
/**
* 分析4:Activity.onTouchEvent()
* 定义:属于顶层View(DecorView)
* 说明:
* a. DecorView类是PhoneWindow类的一个内部类
* b. DecorView继承自FrameLayout,是所有界面的父类
* c. FrameLayout是ViewGroup的子类,故DecorView的间接父类 = ViewGroup
*/
public boolean onTouchEvent(MotionEvent event) {
// 当一个点击事件未被Activity下任何一个View接收 / 处理时
// 应用场景:处理发生在Window边界外的触摸事件
// ->> 分析5
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
// 即 只有在点击事件在Window边界外才会返回true,一般情况都返回false,分析完毕
}
/**
* 分析5:mWindow.shouldCloseOnTouch(this, event)
*/
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
// 主要是对于处理边界外点击事件的判断:是否是DOWN事件,event的坐标是否在边界内等
if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
&& isOutOfBounds(context, event) && peekDecorView() != null) {
return true;
}
return false;
// 返回true:说明事件在边界外,即 消费事件
// 返回false:未消费(默认)
}
ViewGroup中的事件分发
1 | /** |
View事件的分发机制
1 | /** |
总结
当一个事件在屏幕上产生时,首先由Activity的dispatchTouchEvent传递到ViewGroup(一般不会在Activity里就直接拦截),该事件首先要在ViewGroup的dispatchTouchEvent方法中进行判断是否拦截,如果拦截就直接在ViewGroup中消费,不会传递到子View,如果不拦截则通过遍历的方式找到点击的控件View,并将事件传递从ViewGroup传递到View中。
事件在View也是首先调用dispatchTouchEvent方法,该方法会返回boolean值,会返回到ViewGroup中,如果为true则ViewGroup的touch事件拦截(即ViewGroup后面的代码不会执行)。事件也会在View中消费掉。这里的消费是是指调用了当前的touch事件,会不会调用click还看情况。