一.基础认知
1.事件分发的对象
- 在之前的两篇文章中,我们经常提到事件消费。我们的对象就是这些事件(点击事件)。
- 当用户触摸屏幕时(
View
或ViewGroup
派生的控件),将产生点击事件(Touch
事件) Touch
事件的相关细节(发生触摸的位置、时间等)被封装成MotionEvent
对象
2.事件类型
事件类型 | 具体动作 |
---|---|
MotionEvent.ACTION_DOWN | 按下View(所有事件的开始) |
MotionEvent.ACTION_MOVE | 滑动View |
MotionEvent.ACTION_UP | 抬起View(与DOWN对应) |
MotionEvent.ACTION_CANCEL | 结束事件(非人为原因) |
3.事件分发的本质
将点击事件(MotionEvent)传递到某个具体的View
& 处理的整个过程
4.事件分发的顺序
事件传递的顺序:Activity
-> ViewGroup
-> View
二. Activity的事件分发机制
一般情况下事件的开始都是 ACTION_DOWN,都会调用 onUserInteraction方法。而该方法是空的,当此activity在栈顶时,触屏点击按home,back,menu键等都会触发此方法,实现屏保功能。
1
2
3
4
5
6
7
8
9
10
11
12
13public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
public void onUserInteraction() {
}getWindow() = 获取Window类的对象,Window类是抽象类,其唯一实现类 = PhoneWindow类;即此处的Window类对象 = PhoneWindow类对象。Window类的superDispatchTouchEvent() = 1个抽象方法,由子类PhoneWindow类实现。
1
2
3
4
5
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}mDecor 是 顶层View(DecorView)的实例对象,DecorView类是PhoneWindow类的一个内部类,DecorView继承自FrameLayout,是所有界面的父类。FrameLayout是ViewGroup的子类,故DecorView的间接父类 = ViewGroup
1
2
3
4
5public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}调用父类的方法 = ViewGroup的dispatchTouchEvent(),即将事件传递到ViewGroup去处理,详细请看上一篇文章ViewGroup的事件分发机制。
若getWindow().superDispatchTouchEvent(ev)的返回true,则Activity.dispatchTouchEvent()就返回true,则方法结束。即 :该点击事件停止往下传递 & 事件传递过程结束,否则:继续往下调用Activity.onTouchEvent。
三. 事件分发的顺序
图片可能有错误,或者有表达不清楚的地方,仅仅只是展现自己理解程度。
四. Demo证明
- 在一个界面的一个布局里面放置两个按钮。这样,Activity,VIewGroup,View都出现了。
1 |
|
因为,View 和 ViewGroup 的 OnTouchEvent事件 都是在 内部实现的,所以我们需要自定义 ViewGroup 和 View,并在里面实现 OnTouchEvent事件。
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
27public class MyLinearLayout extends LinearLayout {
public static final String TAG = "MainActivity";
public MyLinearLayout(Context context) {
super(context);
}
public MyLinearLayout(Context context, AttributeSet attrs){
super(context, attrs);
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
//拦截事件,交给自己处理,super.dispatchTouchEvent()
//若有OnTouch,交给OnTouch
//否则交给OnTouchEvent
return false;
}
public boolean onTouchEvent(MotionEvent event) {
//不消费事件,交给Activity
Log.d(TAG,"ViewGroup OnTouchEvent"+event.getAction());
return super.onTouchEvent(event);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class MyButton extends androidx.appcompat.widget.AppCompatButton {
public static final String TAG = "MainActivity";
public MyButton(Context context) {
super(context);
}
public MyButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG,"View OnTouchEvent"+event.getAction());
return super.onTouchEvent(event);
}
}给两个 按钮 以及 布局 都设置一个监听事件。
1 | public class MainActivity extends AppCompatActivity { |
4.运行效果
① 点击空白处
1 | 2020-03-25 00:22:07.049 30837-30837/swu.xl.viewgroup_touch D/MainActivity: ViewGroup OnTouch0 |
如果我们在MyLinearLayout中将OnTouchEvent方法中的return super.onTouchEvent(event),换成return false。
1 | 2020-03-25 00:27:51.924 1008-1008/swu.xl.viewgroup_touch D/MainActivity: ViewGroup OnTouch0 |
我们可以看到没有更换之前,触摸事件是被ViewGroup的OnTouchEvent消费了。如果我们改为不消费,就会成功的进入到Activity的OnTouchEvent方法里面,符合我们之前的说法。
② 点击 Button 1
1 | 2020-03-25 00:22:47.246 30837-30837/swu.xl.viewgroup_touch D/MainActivity: View Btn1 OnTouch0 |
Button 1在OnTouch方法里面已经消费了事件,所以事件无法传递,符合我们之前的说法。
③ 点击 Button 2
1 | 2020-03-25 00:23:25.169 30837-30837/swu.xl.viewgroup_touch D/MainActivity: View Btn2 OnTouch0 |
Button 2 在OnTouch方法里面没有消费事件,所以事件传递到 View 的 OnTouchEvent里面被消费了,符合我们之前的说法。