一、概述
1、了解并掌握View的事件分发,可以让我们解决一些view的事件冲突和做一些自定义view的事件。我将以测试代码打印的log的形式去跟踪并了解事件分发的过程。了解事件分发,需要了解view三个重要的函数。
public boolean dispatchTouchEvent(MotionEvent ev)
public boolean onInterceptTouchEvent(MotionEvent ev)(view的这个方法,viewGroup有)
public boolean onTouchEvent(MotionEvent event)
viewGroup和view以树的结构组织起来,当手指触摸view的时候,将或触发这三个方法,按照某些规则,进行事件的传递分发。而我们所要知道的就是这些规则,来知道为什么,view的事件何时被拦截了,父view为何没有触发事件。
二、测试过程
首先定义两个viewGroup
public class ViewGroupOne extends LinearLayout {
String TAG = "ViewGroupOne";
public ViewGroupOne(Context context) {
super(context);
}
public ViewGroupOne(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(Constants.LOG_TAG,TAG+":"+"dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.i(Constants.LOG_TAG,TAG+":"+"onInterceptTouchEvent");
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(Constants.LOG_TAG,TAG+":"+"onTouchEvent");
return super.onTouchEvent(event);
}
}
public class ViewGroupTwo extends LinearLayout {
String TAG = "ViewGroupTwo";
public ViewGroupTwo(Context context) {
super(context);
}
public ViewGroupTwo(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(Constants.LOG_TAG,TAG+":"+"dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.i(Constants.LOG_TAG,TAG+":"+"onInterceptTouchEvent");
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(Constants.LOG_TAG,TAG+":"+"onTouchEvent");
return super.onTouchEvent(event);
}
}
再定义一个view
public class ViewOne extends AppCompatButton {
String TAG = "ViewOne";
public ViewOne(Context context) {
super(context);
}
public ViewOne(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(Constants.LOG_TAG,TAG+":"+"dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(Constants.LOG_TAG,TAG+":"+"onTouchEvent");
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.i(Constants.LOG_TAG,TAG+":"+"ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(Constants.LOG_TAG,TAG+":"+"ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(Constants.LOG_TAG,TAG+":"+"ACTION_UP");
break;
default:
}
return super.onTouchEvent(event);
}
}
将他们嵌套起来,看看他们的事件分发
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<dome.cjk.domeproject.viewTest.ViewGroupOne
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent">
<dome.cjk.domeproject.viewTest.ViewGroupTwo
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<dome.cjk.domeproject.viewTest.ViewOne
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="点击我"
android:id="@+id/btn_testview"/>
</dome.cjk.domeproject.viewTest.ViewGroupTwo>
</dome.cjk.domeproject.viewTest.ViewGroupOne>
</android.support.constraint.ConstraintLayout>
好了,我们结构树组织起来了,点击ViewOne打印下日志看看viewGroup和view之间这三个函数是按照什么顺序来执行的。
01-17 14:56:41.235 22319-22319/dome.cjk.domeproject I/doem_test: ViewGroupOne:dispatchTouchEvent
01-17 14:56:41.235 22319-22319/dome.cjk.domeproject I/doem_test: ViewGroupOne:onInterceptTouchEvent
01-17 14:56:41.235 22319-22319/dome.cjk.domeproject I/doem_test: ViewGroupTwo:dispatchTouchEvent
01-17 14:56:41.235 22319-22319/dome.cjk.domeproject I/doem_test: ViewGroupTwo:onInterceptTouchEvent
01-17 14:56:41.235 22319-22319/dome.cjk.domeproject I/doem_test: ViewOne:dispatchTouchEvent
01-17 14:56:41.235 22319-22319/dome.cjk.domeproject I/doem_test: ViewOne:onTouchEvent
01-17 14:56:41.235 22319-22319/dome.cjk.domeproject I/doem_test: ViewOne:ACTION_DOWN
01-17 14:56:41.242 22319-22319/dome.cjk.domeproject I/doem_test: ViewGroupOne:dispatchTouchEvent
01-17 14:56:41.242 22319-22319/dome.cjk.domeproject I/doem_test: ViewGroupOne:onInterceptTouchEvent
01-17 14:56:41.242 22319-22319/dome.cjk.domeproject I/doem_test: ViewGroupTwo:dispatchTouchEvent
01-17 14:56:41.242 22319-22319/dome.cjk.domeproject I/doem_test: ViewGroupTwo:onInterceptTouchEvent
01-17 14:56:41.242 22319-22319/dome.cjk.domeproject I/doem_test: ViewOne:dispatchTouchEvent
01-17 14:56:41.242 22319-22319/dome.cjk.domeproject I/doem_test: ViewOne:onTouchEvent
01-17 14:56:41.242 22319-22319/dome.cjk.domeproject I/doem_test: ViewOne:ACTION_MOVE
01-17 14:56:41.260 22319-22319/dome.cjk.domeproject I/doem_test: ViewGroupOne:dispatchTouchEvent
01-17 14:56:41.260 22319-22319/dome.cjk.domeproject I/doem_test: ViewGroupOne:onInterceptTouchEvent
01-17 14:56:41.260 22319-22319/dome.cjk.domeproject I/doem_test: ViewGroupTwo:dispatchTouchEvent
01-17 14:56:41.260 22319-22319/dome.cjk.domeproject I/doem_test: ViewGroupTwo:onInterceptTouchEvent
01-17 14:56:41.260 22319-22319/dome.cjk.domeproject I/doem_test: ViewOne:dispatchTouchEvent
01-17 14:56:41.260 22319-22319/dome.cjk.domeproject I/doem_test: ViewOne:onTouchEvent
01-17 14:56:41.260 22319-22319/dome.cjk.domeproject I/doem_test: ViewOne:ACTION_UP
在默认的情况下,我们点击一个view发生了以下过程,所以是最上层的view(最上层的是PhoneWindow)接收到view的按下事件ACTION_DOWN,
然后依次,从上至下传递子viewGroup或view,触发dispatchTouchEvent《 onInterceptTouchEvent,直到最底层的view的dispatchTouchEvent《 onTouchEvent。。。最终默认被view消费。
其实这个分发过程跟dispatchTouchEvent和onInterceptTouchEvent、onTouchEvent的分发过程的返回值有很大的关系。返回代表消费这个事件了,不会往后传了,返回false代表没有消费这个事件,可以继续往下面传。当onTouchEvent返回为true的时候,表示被当前的view消费掉,而让不再父ViewGroup去处理,默认情况下,view的onTouchEvent返回为true,不会往父ViewGroup传,如果返回为false的话,事件没有被消费掉,而依次往上,被父ViewGroup消费,父ViewGroup不消费,就让父ViewGroup的父ViewGroup去消费。让我们修改一下代码验证上面的onTouchEvent消费的过程。
修改一下viewOne的OnTouchEvent的返回值为false
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(Constants.LOG_TAG,TAG+":"+"onTouchEvent");
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.i(Constants.LOG_TAG,TAG+":"+"ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(Constants.LOG_TAG,TAG+":"+"ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(Constants.LOG_TAG,TAG+":"+"ACTION_UP");
break;
default:
}
return false;
}
重新再点击viewOne打印下日志
01-17 15:27:50.053 25579-25579/dome.cjk.domeproject I/doem_test: ViewGroupOne:dispatchTouchEvent
01-17 15:27:50.054 25579-25579/dome.cjk.domeproject I/doem_test: ViewGroupOne:onInterceptTouchEvent
01-17 15:27:50.054 25579-25579/dome.cjk.domeproject I/doem_test: ViewGroupTwo:dispatchTouchEvent
01-17 15:27:50.054 25579-25579/dome.cjk.domeproject I/doem_test: ViewGroupTwo:onInterceptTouchEvent
01-17 15:27:50.054 25579-25579/dome.cjk.domeproject I/doem_test: ViewOne:dispatchTouchEvent
01-17 15:27:50.054 25579-25579/dome.cjk.domeproject I/doem_test: ViewOne:onTouchEvent
01-17 15:27:50.054 25579-25579/dome.cjk.domeproject I/doem_test: ViewOne:ACTION_DOWN
01-17 15:27:50.054 25579-25579/dome.cjk.domeproject I/doem_test: ViewGroupTwo:onTouchEvent
01-17 15:27:50.054 25579-25579/dome.cjk.domeproject I/doem_test: ViewGroupOne:onTouchEvent
父view依次都执行了onTouchEvent。
修改一下viewGroup的
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(Constants.LOG_TAG,TAG+":"+"onTouchEvent");
return true;
}
打印
01-17 15:32:17.399 26214-26214/dome.cjk.domeproject I/doem_test: ViewGroupOne:dispatchTouchEvent
01-17 15:32:17.400 26214-26214/dome.cjk.domeproject I/doem_test: ViewGroupOne:onInterceptTouchEvent
01-17 15:32:17.400 26214-26214/dome.cjk.domeproject I/doem_test: ViewGroupTwo:dispatchTouchEvent
01-17 15:32:17.400 26214-26214/dome.cjk.domeproject I/doem_test: ViewGroupTwo:onInterceptTouchEvent
01-17 15:32:17.400 26214-26214/dome.cjk.domeproject I/doem_test: ViewOne:dispatchTouchEvent
01-17 15:32:17.400 26214-26214/dome.cjk.domeproject I/doem_test: ViewOne:onTouchEvent
01-17 15:32:17.400 26214-26214/dome.cjk.domeproject I/doem_test: ViewOne:ACTION_DOWN
01-17 15:32:17.400 26214-26214/dome.cjk.domeproject I/doem_test: ViewGroupTwo:onTouchEvent
事件在ViewGroupTwo消费掉了,ViewGroupOne就没有走onTouchEvent了。很多事件冲突问题,就是因为被子view消费掉了,而父view就得不到的方法了。
那可不可以在子view触发onTouchEvent之前通过onInterceptTouchEvent就拦截事件不再往下传呢?答案是肯定的,从onInterceptTouchEvent的字面意思,我们都可以知道,他是用来拦截TouchEvent的方法的。通过onInterceptTouchEvent也可以消费事件。onInterceptTouchEvent默认为false,表示不中断,而为true的话表示中断,我们先修改一下ViewOne的onInterceptTouchEvent的返回值为true,看下了打印了什么。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.i(Constants.LOG_TAG,TAG+":"+"onInterceptTouchEvent");
return true;
}
打印
01-17 15:43:26.770 28718-28718/dome.cjk.domeproject I/doem_test: ViewGroupOne:dispatchTouchEvent
01-17 15:43:26.770 28718-28718/dome.cjk.domeproject I/doem_test: ViewGroupOne:onInterceptTouchEvent
01-17 15:43:26.770 28718-28718/dome.cjk.domeproject I/doem_test: ViewGroupOne:onTouchEvent
拦截了直接去先让走自己onTouchEvent方法,不让其子view去触发消费功能了。
在action_down dispatchTouchEvent返回true的时候表示消费这个事件,表示不再往上面传递
那修改dispatchTouchEvent的返回值又是发生了什么呢,让我们拭目以待
修改一下
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(Constants.LOG_TAG,TAG+":"+"dispatchTouchEvent");
super.dispatchTouchEvent(ev);
return true;
}
打印
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(Constants.LOG_TAG,TAG+":"+"dispatchTouchEvent");
super.dispatchTouchEvent(ev);
return true;
}
01-17 15:53:58.360 32168-32168/dome.cjk.domeproject I/doem_test: ViewGroupOne:dispatchTouchEvent
01-17 15:53:58.361 32168-32168/dome.cjk.domeproject I/doem_test: ViewGroupOne:onInterceptTouchEvent
01-17 15:53:58.361 32168-32168/dome.cjk.domeproject I/doem_test: ViewGroupTwo:dispatchTouchEvent
01-17 15:53:58.361 32168-32168/dome.cjk.domeproject I/doem_test: ViewGroupTwo:onInterceptTouchEvent
01-17 15:53:58.361 32168-32168/dome.cjk.domeproject I/doem_test: ViewOne:dispatchTouchEvent
01-17 15:53:58.361 32168-32168/dome.cjk.domeproject I/doem_test: ViewOne:onTouchEvent
01-17 15:53:58.361 32168-32168/dome.cjk.domeproject I/doem_test: ViewOne:ACTION_DOWN
01-17 15:53:58.361 32168-32168/dome.cjk.domeproject I/doem_test: ViewGroupTwo:onTouchEvent
01-17 15:53:58.361 32168-32168/dome.cjk.domeproject I/doem_test: ViewGroupOne:onTouchEvent
01-17 15:53:58.363 32168-32168/dome.cjk.domeproject I/doem_test: ViewGroupOne:dispatchTouchEvent
01-17 15:53:58.363 32168-32168/dome.cjk.domeproject I/doem_test: ViewGroupOne:onTouchEvent
01-17 15:53:58.364 32168-32168/dome.cjk.domeproject I/doem_test: ViewGroupOne:dispatchTouchEvent
01-17 15:53:58.364 32168-32168/dome.cjk.domeproject I/doem_test: ViewGroupOne:onTouchEvent
第二次打印
01-17 15:53:58.364 32168-32168/dome.cjk.domeproject I/doem_test: ViewGroupOne:dispatchTouchEvent
01-17 15:53:58.364 32168-32168/dome.cjk.domeproject I/doem_test: ViewGroupOne:onTouchEvent
直接走了ViewGroupOne的onTouchEvent方法。说明dispatchTouchEvent方法一定程度上可以拦截事件的。
假如某一个view在ontouchevent那里消费了,当action_up的时候,事件dispathchTouchEvent传到消费的view的ontouchview那里去,就不往下传了。
对于ACTION_MOVE和ACTION_UP在不同函数中的传递,有以下结论:
-
结论1:对于dispatchTouchEvent :返回值为true时,自己消费事件。因为返回值为true代表消费,事件不会往下面传,因此ACTION_DOWN事件传递到此处停止传递,ACTION_MOVE和ACTION_UP也传递到此处停止向下传递,这个时候传递方向是一致的。
-
结论2:对于onTouchEvent :返回值为true时,自己消费事件。因为事件传递到onTouchEvent有可能是下层View或ViewGroup回传过来的,这时候ACTION_DOWN是经过下层传递回来的,但是此时ACTION_MOVE和ACTION_UP并不会传递到下层;也有可能是自身的onInterceptTouchEvent 返回了true传递过来的,这个时候ACTION_MOVE和ACTION_UP和ACTION_DOWN事件的传递流程也是一样的。
对于ACTION_MOVE、ACTION_UP终极总结:
ACTION_DOWN事件在哪个控件消费了(return true), 那么ACTION_MOVE和ACTION_UP就会从上往下(通过dispatchTouchEvent)做事件分发往下传,就只会传到这个控件,不会继续往下传,如果ACTION_DOWN事件是在dispatchTouchEvent消费,那么事件到此为止停止传递,如果ACTION_DOWN事件是在onTouchEvent消费的,那么会把ACTION_MOVE或ACTION_UP事件传给该控件的onTouchEvent处理并结束传递。
网上有篇文章https://www.jianshu.com/p/e99b5e8bd67b写得挺好了。