由于 Android 的各个 View 是层层重叠的,那么当在以下图的位置点击时,这个点击事件究竟要给谁处理呢?css
这个时候就须要事件分发机制来处理了。android
说白了,事件分发其实就是决定将点击事件分发给谁处理的一套规则。bash
这里先抛出几个问题,不知道你们有没有遇到过滑动冲突,下面举出三个滑动冲突场景:app
图中有两个 View,外部的 View 是横向滑动的,而内部的 View 是竖向滑动的。这个时候在内部的 View 进行滑动,怎么对这个事件进行分发呢?ide
如今外部的 View 和内部的 View 的滑动方向是同样的,这个时候在内部的 View 滑动,这时候怎么解决滑动冲突呢?oop
若是是前面两个场景同时混合,这时候又怎么分发呢?学习
好的,带着这几个问题如下来说解事件分发。测试
在学习事件分发必需要理解几个关键的概念。ui
当手指点击屏幕的时候,就会产生事件,这些事件的信息都在 MotionEvent 这个类中,事件的种类以下表:spa
事件种类 | 意思 |
---|---|
MotionEvent.ACTION_DOWN | 手指按下 View |
MotionEvent.ACTION_MOVE | 手指在 View 滑动 |
MotionEvent.ACTION_UP | 手指抬起 |
一个事件序列就是从手指按下 View 开始直到手指离开 View 产生的一系列事件。
其实这里的意思就是以 DOWN 事件开始,中间产生无数个 MOVE 事件,最后以 UP 事件结束。
Activity -> ViewGroup -> ... -> View
方法 | 做用 |
---|---|
dispathchTouchEvent() | 分发点击事件 |
onInterceptTouchEvent() | 判断是否拦截点击事件 |
onTouchEvent() | 处理点击事件 |
要注意的是 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:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.example.administrator.toucheventdemo.ViewGroup1
android:layout_width="300dp"
android:layout_height="300dp"
android:background="#00ff00">
<com.example.administrator.toucheventdemo.View1
android:layout_width="200dp"
android:layout_height="200dp"
android:background="#ff0000"/>
</com.example.administrator.toucheventdemo.ViewGroup1>
</android.support.constraint.ConstraintLayout>
复制代码
这里返回值会有三种可能性:
打印结果:
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 事件,那么同一个事件序列剩下的事件将不会再交给它处理,而会交给它的父元素处理。
打印结果:
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 处理事件。
打印结果:
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() 返回默认值。
前面已经探究过 ViewGroup 返回默认值的状况了,如今只看看返回 true 和 false 的状况。
打印结果:
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
复制代码
根据打印结果画出的流程图:
打印结果:
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 事件,事件序列接下来的事件都不会再交给它处理。
这里要说明一下的是 onInterceptTouchEvent() 默认返回值是 false,因此这里只探究返回 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 事件,那么事件序列的其余事件就不会再给到它了。
若是是这种状况,就会直接将事件分发给下一个 View,上面已经说过了,这里就再也不赘述了。
到这里其实 View 的分发事件与 ViewGroup 是基本类似的,View 的 dispathchTouchEvent() 返回 true 就表明消费该事件,返回 false 就表明不处理事件,返回默认值就将事件传递给 onTouchEvent()。
从以上结果就能够获得总的流程图:
其实从上面的探究能够得出一些结论:
这里还要验证一个结论,就是若是 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()。
参考文章: