1. 什么是事件分发?
因为 Android 的各个 View 是层层重叠的,那么当在如下图的位置点击时,这个点击事件究竟要给谁处理呢?
这个时候就需要事件分发机制来处理了。
说白了,事件分发其实就是决定将点击事件分发给谁处理的一套规则。
2. 事件分发使用场景
这里先抛出几个问题,不知道大家有没有遇到过滑动冲突,下面举出三个滑动冲突场景:
2.1 场景一
图中有两个 View,外部的 View 是横向滑动的,而内部的 View 是竖向滑动的。这个时候在内部的 View 进行滑动,怎么对这个事件进行分发呢?
2.2 场景二
现在外部的 View 和内部的 View 的滑动方向是一样的,这个时候在内部的 View 滑动,这时候怎么解决滑动冲突呢?
2.3 场景三
如果是前面两个场景同时混合,这时候又怎么分发呢?
好的,带着这几个问题以下来讲解事件分发。
3. 事件分发的基本概念
在学习事件分发必须要理解几个关键的概念。
3.1 什么是事件?
当手指点击屏幕的时候,就会产生事件,这些事件的信息都在 MotionEvent 这个类中,事件的种类如下表:
事件种类 | 意思 |
---|---|
MotionEvent.ACTION_DOWN | 手指按下 View |
MotionEvent.ACTION_MOVE | 手指在 View 滑动 |
MotionEvent.ACTION_UP | 手指抬起 |
3.2 什么是事件序列?
一个事件序列就是从手指按下 View 开始直到手指离开 View 产生的一系列事件。
其实这里的意思就是以 DOWN 事件开始,中间产生无数个 MOVE 事件,最后以 UP 事件结束。
3.3 事件序列的传递顺序
Activity -> ViewGroup -> ... -> View
3.4 事件序列关键方法
方法 | 作用 |
---|---|
dispathchTouchEvent() | 分发点击事件 |
onInterceptTouchEvent() | 判断是否拦截点击事件 |
onTouchEvent() | 处理点击事件 |
4. 事件分发的关键概念
4.1 事件分发流程
要注意的是 Activity 和 View 是没有 onInterceptTouchEvent() 方法的。
现在探究一下事件分发的流程是怎么样的。
探究的过程就使用一步步打印,并且改变相关的返回值,然后画出局部的流程图,最后得出全局的流程图。
这里先看看测试代码:
Util:
public class Util {
public static String getAction(MotionEvent event) {
String action = "";
if (event.getAction() == MotionEvent.ACTION_DOWN) {
action = "ACTION_DOWN";
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
action = "ACTION_MOVE";
} else if (event.getAction() == MotionEvent.ACTION_UP) {
action = "ACTION_UP";
}
return action;
}
}
这个类的作用只是让打印可以打印出来具体是哪个点击事件。
MainActivity:
public class MainActivity extends AppCompatActivity {
public String TAG = "chan";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d(TAG, "=================Activity dispatchTouchEvent Action: "
+ Util.getAction(ev));
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
Log.d(TAG, "=================Activity onTouchEvent Action: "
+ Util.getAction(ev));
return super.onTouchEvent(ev);
}
}
ViewGroup1:
public class ViewGroup1 extends LinearLayout {
public String TAG = "chan";
public ViewGroup1(Context context) {
super(context);
}
public ViewGroup1(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d(TAG, "=================ViewGroup dispatchTouchEvent Action: "
+ Util.getAction(ev));
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d(TAG, "=================ViewGroup onInterceptTouchEvent Action: "
+ Util.getAction(ev));
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG, "=================ViewGroup onTouchEvent Action: "
+ Util.getAction(event));
return super.onTouchEvent(event);
}
}
View1:
public class View1 extends View {
public String TAG = "chan";
public View1(Context context) {
super(context);
}
public View1(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.d(TAG, "=================View dispatchTouchEvent Action: "
+ Util.getAction(event));
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result = super.onTouchEvent(event);
Log.d(TAG, "=================View onTouchEvent Action: "
+ Util.getAction(event));
return super.onTouchEvent(event);
}
}
activity_main.xml:
4.1.1 改变 Activity 的 dispathchTouchEvent() 方法的返回值
这里返回值会有三种可能性:
- super.dispatchTouchEvent(ev)
- true
- false
4.1.1.1 返回 super.dispatchTouchEvent(ev)
打印结果:
08-09 09:49:03.167 26886-26886/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_DOWN
08-09 09:49:03.168 26886-26886/com.example.administrator.toucheventdemo D/chan: =================ViewGroup dispatchTouchEvent Action: ACTION_DOWN
=================ViewGroup onInterceptTouchEvent Action: ACTION_DOWN
=================View dispatchTouchEvent Action: ACTION_DOWN
08-09 09:49:03.169 26886-26886/com.example.administrator.toucheventdemo D/chan: =================View onTouchEvent Action: ACTION_DOWN
=================ViewGroup onTouchEvent Action: ACTION_DOWN
=================Activity onTouchEvent Action: ACTION_DOWN
08-09 09:49:03.190 26886-26886/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_MOVE
=================Activity onTouchEvent Action: ACTION_MOVE
08-09 09:49:03.196 26886-26886/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_MOVE
=================Activity onTouchEvent Action: ACTION_MOVE
08-09 09:49:03.197 26886-26886/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_UP
08-09 09:49:03.197 26886-26886/com.example.administrator.toucheventdemo D/chan: =================Activity onTouchEvent Action: ACTION_UP
根据打印结果画出的流程图:
上面的流程图只是画了 DOWN 事件的分发过程,从打印结果可以看出后面的 MOVE 和 UP 事件,Activity 并没有分发下去,而是自己处理了。这里可以得出一个结论:
如果一个 View 不消费 DOWN 事件,那么同一个事件序列剩下的事件将不会再交给它处理,而会交给它的父元素处理。
4.1.1.2 返回 true
打印结果:
08-09 10:11:19.533 28137-28137/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_DOWN
08-09 10:11:19.546 28137-28137/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_MOVE
08-09 10:11:19.569 28137-28137/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_MOVE
08-09 10:11:19.572 28137-28137/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_UP
如果直接返回 true,不会分发任何事件,可以在 Activity 处理事件。
4.1.1.3 返回 false
打印结果:
08-09 10:13:54.394 28415-28415/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_DOWN
08-09 10:13:54.442 28415-28415/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_MOVE
08-09 10:13:54.444 28415-28415/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_UP
如果直接返回 false,不会分发任何事件,可以在 Activity 处理事件。
从这里就可以看出,Acitity 如果要将事件分发出去,必须在 dispatchTouchEvent() 返回默认值。
4.1.2 改变 ViewGroup 的 dispathchTouchEvent() 方法的返回值
前面已经探究过 ViewGroup 返回默认值的情况了,现在只看看返回 true 和 false 的情况。
4.1.2.1 返回 true
打印结果:
08-09 10:20:51.658 29295-29295/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_DOWN
08-09 10:20:51.659 29295-29295/com.example.administrator.toucheventdemo D/chan: =================ViewGroup dispatchTouchEvent Action: ACTION_DOWN
08-09 10:20:51.690 29295-29295/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_MOVE
08-09 10:20:51.691 29295-29295/com.example.administrator.toucheventdemo D/chan: =================ViewGroup dispatchTouchEvent Action: ACTION_MOVE
08-09 10:20:51.699 29295-29295/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_MOVE
=================ViewGroup dispatchTouchEvent Action: ACTION_MOVE
08-09 10:20:51.702 29295-29295/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_UP
=================ViewGroup dispatchTouchEvent Action: ACTION_UP
根据打印结果画出的流程图:
4.1.2.1 返回 false
打印结果:
08-09 10:25:27.046 29672-29672/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_DOWN
08-09 10:25:27.047 29672-29672/com.example.administrator.toucheventdemo D/chan: =================ViewGroup dispatchTouchEvent Action: ACTION_DOWN
08-09 10:25:27.048 29672-29672/com.example.administrator.toucheventdemo D/chan: =================Activity onTouchEvent Action: ACTION_DOWN
08-09 10:25:27.062 29672-29672/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_MOVE
=================Activity onTouchEvent Action: ACTION_MOVE
08-09 10:25:27.078 29672-29672/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_MOVE
=================Activity onTouchEvent Action: ACTION_MOVE
08-09 10:25:27.088 29672-29672/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_MOVE
=================Activity onTouchEvent Action: ACTION_MOVE
08-09 10:25:27.091 29672-29672/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_UP
=================Activity onTouchEvent Action: ACTION_UP
从结果可以看出,如果 ViewGroup 不消费 DOWN 事件,事件序列接下来的事件都不会再交给它处理。
4.1.3 改变 ViewGroup 的 onInterceptTouchEvent() 方法的返回值
这里要说明一下的是 onInterceptTouchEvent() 默认返回值是 false,所以这里只探究返回 true 和 默认值就可以了。
4.1.3.1 返回 true
打印结果:
08-09 10:31:52.499 30168-30168/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_DOWN
08-09 10:31:52.501 30168-30168/com.example.administrator.toucheventdemo D/chan: =================ViewGroup dispatchTouchEvent Action: ACTION_DOWN
=================ViewGroup onInterceptTouchEvent Action: ACTION_DOWN
=================ViewGroup onTouchEvent Action: ACTION_DOWN
=================Activity onTouchEvent Action: ACTION_DOWN
08-09 10:31:52.515 30168-30168/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_UP
=================Activity onTouchEvent Action: ACTION_UP
流程图:
上面的打印结果是 ViewGroup 的 onTouchEvent 返回默认值的情况,但是上面的流程图可以知道,如果 onTouchEvent 返回默认值或者 false,代表不消耗 DOWN 事件,那么事件序列的其他事件就不会再给到它了。
4.1.3.2 返回 false 或者 默认值
如果是这种情况,就会直接将事件分发给下一个 View,上面已经说过了,这里就不再赘述了。
到这里其实 View 的分发事件与 ViewGroup 是基本相似的,View 的 dispathchTouchEvent() 返回 true 就代表消费该事件,返回 false 就代表不处理事件,返回默认值就将事件传递给 onTouchEvent()。
从以上结果就可以得到总的流程图:
4.2 事件分发的一些结论
其实从上面的探究可以得出一些结论:
- 如果一个 View 不消费 DOWN 事件,那么同一个事件序列剩下的事件将不会再交给它处理,而会交给它的父元素处理
- 如果一个 ViewGroup 决定拦截事件(onInterceptTouchEvent 返回 true),那么 onInterceptTouchEvent() 就不会再被调用
这里还要验证一个结论,就是如果 View 不消费除了 DOWN 事件的其他事件,那么这些事件就会直接交给 Activity 的 onTouchEvent() 处理,而不会再交给它的父容器的 onTouchEvent()。
这里修改下 View1 的代码:
public class View1 extends View {
public String TAG = "chan";
public View1(Context context) {
super(context);
}
public View1(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.d(TAG, "=================View dispatchTouchEvent Action: "
+ Util.getAction(event));
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result = super.onTouchEvent(event);
Log.d(TAG, "=================View onTouchEvent Action: "
+ Util.getAction(event));
if(event.getAction() != MotionEvent.ACTION_DOWN) {
return false;
}
return true;
}
}
可以看到代码我只是修改了 onTouchEvent() 的代码,这里只消费 DOWN 事件,其余事件都不消费。
打印结果:
08-09 11:18:52.846 3098-3098/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_DOWN
08-09 11:18:52.847 3098-3098/com.example.administrator.toucheventdemo D/chan: =================ViewGroup dispatchTouchEvent Action: ACTION_DOWN
08-09 11:18:52.848 3098-3098/com.example.administrator.toucheventdemo D/chan: =================ViewGroup onInterceptTouchEvent Action: ACTION_DOWN
=================View dispatchTouchEvent Action: ACTION_DOWN
=================View onTouchEvent Action: ACTION_DOWN
08-09 11:18:52.863 3098-3098/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_MOVE
08-09 11:18:52.864 3098-3098/com.example.administrator.toucheventdemo D/chan: =================ViewGroup dispatchTouchEvent Action: ACTION_MOVE
=================ViewGroup onInterceptTouchEvent Action: ACTION_MOVE
=================View dispatchTouchEvent Action: ACTION_MOVE
=================View onTouchEvent Action: ACTION_MOVE
=================Activity onTouchEvent Action: ACTION_MOVE
08-09 11:18:52.877 3098-3098/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_UP
=================ViewGroup dispatchTouchEvent Action: ACTION_UP
=================ViewGroup onInterceptTouchEvent Action: ACTION_UP
=================View dispatchTouchEvent Action: ACTION_UP
08-09 11:18:52.878 3098-3098/com.example.administrator.toucheventdemo D/chan: =================View onTouchEvent Action: ACTION_UP
=================Activity onTouchEvent Action: ACTION_UP
从打印结果可以看出,除了 DOWN 事件外,其余事件就直接交给 Activity 处理,并没有再回调 ViewGroup 的 onTouchEvent()。
参考文章:
《Android开发艺术探索》
Android事件分发机制 详解攻略,您值得拥有
安卓自定义View进阶-事件分发机制原理
安卓自定义View进阶-事件分发机制详解