今天整理下android的事件分发流程,在android中,所有控件都继承自View控件,以及View的子控件ViewGroup,今天说的事件分发机制就是以这两个控件来讲解:
在android的UI布局中,通常最低层都是继承自ViewGroup的容器类控件,最顶层的往往是继承自View的显示类控件,由于UI的需求,往往会出现多层容器嵌套的问题,在极端情况下,如果多层容器都需要处理滑动事件的话,就容易引起滑动事件冲突问题,在这里,我就详细分析下android中事件的传递过程,只要明白了事件传递过程,就可以在实际应用中处理好他们之间的冲突关系了。
滑动事件的传递过程在View以及ViewGroup中有些许不同,View中处理滑动事件跟两个方法有关,dispatchTouchEvent和onTouchEvent,ViewGroup是继承自View,除了上述两个方法外,还多了一个方法,就是onInterceptTouchEvent,下面是事件的传递过程:
View : dispatchTouchEvent -> onTouchEvent;
ViewGroup: dispatchTouchEvent -> onInterceptTouchEvent -> onTouchEvent;
为了清晰明了的分析事件传递过程,我们以实例来说话,下面先定义一个继承自ViewGroup的类,主要就是多了事件传递过程的打印信息,代码如下:
import android.content.Context;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ViewGroup;
/**
* @author: xiewenliang
* @Filename:
* @Description:
* @date: 2017/4/14 14:18
*/
public class EventViewGroup extends ViewGroup {
private String TAG;
public EventViewGroup(Context context, String TAG) {
super(context);
this.TAG = TAG;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (getChildAt(0) != null) getChildAt(0).layout(l, t, r, b);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
Log.i("MotionEvent", TAG + "-dispatchTouchEvent");
}
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
Log.i("MotionEvent", TAG + "-onInterceptTouchEvent");
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
Log.i("MotionEvent", TAG + "-onTouchEvent");
}
return super.onTouchEvent(event);
}
}
再来就是Activity的代码了,如下:
import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.ViewGroup;
import com.example.viewpagertransformer.View.EventView;
import com.example.viewpagertransformer.View.EventViewGroup;
/**
* @author: xiewenliang
* @Filename:
* @Description:
* @date: 2017/4/14 14:17
*/
public class EventActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
EventViewGroup eventViewGroupA = new EventViewGroup(this, "A");
EventViewGroup eventViewGroupB = new EventViewGroup(this, "B");
EventViewGroup eventViewGroupC = new EventViewGroup(this, "C");
eventViewGroupA.setLayoutParams(lp);
eventViewGroupB.setLayoutParams(lp);
eventViewGroupC.setLayoutParams(lp);
eventViewGroupB.addView(eventViewGroupC);
eventViewGroupA.addView(eventViewGroupB);
setContentView(eventViewGroupA);
}
}
Activity中我们的布局是三个容器嵌套在一起, 也就是A包涵B,B包涵C,A在容器的最低层,C在容器的最顶层,我们看一下打印的结果:
04-14 16:45:24.793 9105-9105/com.example.viewpagertransformer I/MotionEvent: A-dispatchTouchEvent
04-14 16:45:24.793 9105-9105/com.example.viewpagertransformer I/MotionEvent: A-onInterceptTouchEvent
04-14 16:45:24.793 9105-9105/com.example.viewpagertransformer I/MotionEvent: B-dispatchTouchEvent
04-14 16:45:24.793 9105-9105/com.example.viewpagertransformer I/MotionEvent: B-onInterceptTouchEvent
04-14 16:45:24.793 9105-9105/com.example.viewpagertransformer I/MotionEvent: C-dispatchTouchEvent
04-14 16:45:24.793 9105-9105/com.example.viewpagertransformer I/MotionEvent: C-onInterceptTouchEvent
04-14 16:45:24.793 9105-9105/com.example.viewpagertransformer I/MotionEvent: C-onTouchEvent
04-14 16:45:24.794 9105-9105/com.example.viewpagertransformer I/MotionEvent: B-onTouchEvent
04-14 16:45:24.794 9105-9105/com.example.viewpagertransformer I/MotionEvent: A-onTouchEvent
大家看到 事件的传递过程 就是从:A层dispatchTouchEvent -> A层onInterceptTouchEvent -> ~~ -> C层dispatchTouchEvent -> C层onInterceptTouchEvent ->
C层onTouchEvent -> ~ -> A层onTouchEvent。
从这里可以分析到 事件是从最底层的dispatchTouchEvent、onInterceptTouchEvent一直到最顶层的dispatchTouchEvent、onInterceptTouchEvent, 然后从最顶层的onTouchEvent回传到最底层的onTouchEvent。
明白了这些后,大家有看到,其实每个事件函数,都会返回一个布尔值,他们返回的布尔值是否会对事件传递产生影响呢? 下面我们通过事件来证明:
首先我们让A容器在dispatchTouchEvent方法中返回true,EventViewGroup的代码更改如下:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
Log.i("MotionEvent", TAG + "-dispatchTouchEvent");
}
if ("A".equals(TAG)) return true;
return super.dispatchTouchEvent(ev);
}
也就是说 当事件传递到A的dispatchTouchEvent方法中,我们返回true,来看看事件传递打印结果:
04-14 16:56:36.706 16544-16544/com.example.viewpagertransformer I/MotionEvent: A-dispatchTouchEvent
我们发现 事件只打印了一条,根据前面我们得到的事件传递顺序,可以得出,如果在dispatchTouchEvent中返回true,事件会在该容器的dispatchTouchEvent中直接消耗,不会传递到该容器的onInterceptTouchEvent方法,也不会继续向下传递,为了证实我们的猜测,我们把它改成在B容器中的dispatchTouchEvent方法中返回true,猜测结果应该是:A层dispatchTouchEvent -> A层onInterceptTouchEvent -> B层dispatchTouchEvent, 事件通过A的两个方法传递到B层就会结束,EventViewGroup代码修改如下:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
Log.i("MotionEvent", TAG + "-dispatchTouchEvent");
}
if ("B".equals(TAG)) return true;
return super.dispatchTouchEvent(ev);
}
再来看看打印结果:
04-14 17:06:00.743 17930-17930/com.example.viewpagertransformer I/MotionEvent: A-dispatchTouchEvent
04-14 17:06:00.743 17930-17930/com.example.viewpagertransformer I/MotionEvent: A-onInterceptTouchEvent
04-14 17:06:00.743 17930-17930/com.example.viewpagertransformer I/MotionEvent: B-dispatchTouchEvent
结果也是证实了我们的猜测。得出的结论就是:如果在容器的dispatchTouchEvent中返回true,事件就会在该容器的dispatchTouchEvent方法中消耗,不会传递给该容器的其它事件处理方法,也不会继续向下传递事件。
接下来我们看看如果dispatchTouchEvent中返回false会是什么样的结果呢,EventViewGroup代码修改如下:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
Log.i("MotionEvent", TAG + "-dispatchTouchEvent");
}
if ("A".equals(TAG)) return false;
return super.dispatchTouchEvent(ev);
}
这里我们更改A容器执行到dispatchTouchEvent方法直接返回false,看下事件打印结果:
04-14 17:12:09.040 23155-23155/com.example.viewpagertransformer I/MotionEvent: A-dispatchTouchEvent
我们看到,打印结果跟返回true是一样的,难道这个方法返回false和true是一样的吗,带着这个猜测,我们继续在B容器中该方法返回false,EventViewGroup代码修改如下:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
Log.i("MotionEvent", TAG + "-dispatchTouchEvent");
}
if ("B".equals(TAG)) return false;
return super.dispatchTouchEvent(ev);
}
打印结果:
04-14 17:15:30.366 26175-26175/com.example.viewpagertransformer I/MotionEvent: A-dispatchTouchEvent
04-14 17:15:30.366 26175-26175/com.example.viewpagertransformer I/MotionEvent: A-onInterceptTouchEvent
04-14 17:15:30.366 26175-26175/com.example.viewpagertransformer I/MotionEvent: B-dispatchTouchEvent
04-14 17:15:30.366 26175-26175/com.example.viewpagertransformer I/MotionEvent: A-onTouchEvent
我们看到,dispatchTouchEvent方法返回true和返回false还是不一样的,返回true的时候,直接事件直接就在该方法内结束了,不会再传递给其它事件处理方法和容器,如果返回false,则不会继续调用该容器的其它事件处理方法,但是事件会提前传递到上层容器的onTouchEvent事件处理方法中。好了,dispatchTouchEvent事件处理机制我们就实验到这里。
下面来实验一下onInterceptTouchEvent方法中的返回值对事件传递有什么影响呢,我们先修改容器A中该方法的返回值为true,EventViewGroup代码修改如下:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
Log.i("MotionEvent", TAG + "-onInterceptTouchEvent");
}
if ("A".equals(TAG)) return true;
return super.onInterceptTouchEvent(ev);
}
打印结果:
04-14 17:24:35.009 1720-1720/com.example.viewpagertransformer I/MotionEvent: A-dispatchTouchEvent
04-14 17:24:35.010 1720-1720/com.example.viewpagertransformer I/MotionEvent: A-onInterceptTouchEvent
04-14 17:24:35.010 1720-1720/com.example.viewpagertransformer I/MotionEvent: A-onTouchEvent
我们看到 事件在容器A中就结束了,按照正常流程,事件传递到A的onInterceptTouchEvent方法,接下来就是B的dispatchTouchEvent方法了,但是这里没有把事件传递到B的dispatchTouchEvent中,而是直接就传递到了A的onTouchEvent方法中,由此,我们猜测,如果在容器的onInterceptTouchEvent方法中返回true,该事件不会继续想子容器或者子控件传递,而是提现就交给了本容器的onTouchEvent方法处理,如果是这样,我们猜测,如果在B容器的onInterceptTouchEvent方法中返回true,事件的传递顺序应该是这样的 A层dispatchTouchEvent -> A层onInterceptTouchEvent -> B层dispatchTouchEvent -> B层onInterceptTouchEvent ->
B层onTouchEvent -> A层onTouchEvent, 而不会传递到C层,为了验证猜测,EventViewGroup代码修改如下:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
Log.i("MotionEvent", TAG + "-onInterceptTouchEvent");
}
if ("B".equals(TAG)) return true;
return super.onInterceptTouchEvent(ev);
}
打印结果:
04-14 17:30:42.210 7308-7308/com.example.viewpagertransformer I/MotionEvent: A-dispatchTouchEvent
04-14 17:30:42.210 7308-7308/com.example.viewpagertransformer I/MotionEvent: A-onInterceptTouchEvent
04-14 17:30:42.210 7308-7308/com.example.viewpagertransformer I/MotionEvent: B-dispatchTouchEvent
04-14 17:30:42.210 7308-7308/com.example.viewpagertransformer I/MotionEvent: B-onInterceptTouchEvent
04-14 17:30:42.211 7308-7308/com.example.viewpagertransformer I/MotionEvent: B-onTouchEvent
04-14 17:30:42.211 7308-7308/com.example.viewpagertransformer I/MotionEvent: A-onTouchEvent
打印结果证明我们的猜测结果是正确的,所以总结如下:
当在容器的onInterceptTouchEvent方法中返回true,事件不会继续向子容器传递,而是提前传递给本容器的onTouchEvent方法。
下面我们来看看onInterceptTouchEvent方法中返回false,会是什么样的流程呢,我们先更改A容器的onInterceptTouchEvent方法返回false, EventViewGroup代码修改如下:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
Log.i("MotionEvent", TAG + "-onInterceptTouchEvent");
}
if ("A".equals(TAG)) return false;
return super.onInterceptTouchEvent(ev);
}
打印结果:
04-14 17:34:15.351 10383-10383/com.example.viewpagertransformer I/MotionEvent: A-dispatchTouchEvent
04-14 17:34:15.351 10383-10383/com.example.viewpagertransformer I/MotionEvent: A-onInterceptTouchEvent
04-14 17:34:15.351 10383-10383/com.example.viewpagertransformer I/MotionEvent: B-dispatchTouchEvent
04-14 17:34:15.351 10383-10383/com.example.viewpagertransformer I/MotionEvent: B-onInterceptTouchEvent
04-14 17:34:15.351 10383-10383/com.example.viewpagertransformer I/MotionEvent: C-dispatchTouchEvent
04-14 17:34:15.351 10383-10383/com.example.viewpagertransformer I/MotionEvent: C-onInterceptTouchEvent
04-14 17:34:15.351 10383-10383/com.example.viewpagertransformer I/MotionEvent: C-onTouchEvent
04-14 17:34:15.351 10383-10383/com.example.viewpagertransformer I/MotionEvent: B-onTouchEvent
04-14 17:34:15.351 10383-10383/com.example.viewpagertransformer I/MotionEvent: A-onTouchEvent
我们发现,打印结果跟默认流程是一致的,是否onInterceptTouchEvent方法中默认返回值就是false呢,我们继续修改B中该方法返回值,EventViewGroup代码修改如下:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
Log.i("MotionEvent", TAG + "-onInterceptTouchEvent");
}
if ("B".equals(TAG)) return false;
return super.onInterceptTouchEvent(ev);
}
打印结果:
04-14 17:36:11.767 12071-12071/com.example.viewpagertransformer I/MotionEvent: A-dispatchTouchEvent
04-14 17:36:11.767 12071-12071/com.example.viewpagertransformer I/MotionEvent: A-onInterceptTouchEvent
04-14 17:36:11.767 12071-12071/com.example.viewpagertransformer I/MotionEvent: B-dispatchTouchEvent
04-14 17:36:11.767 12071-12071/com.example.viewpagertransformer I/MotionEvent: B-onInterceptTouchEvent
04-14 17:36:11.767 12071-12071/com.example.viewpagertransformer I/MotionEvent: C-dispatchTouchEvent
04-14 17:36:11.767 12071-12071/com.example.viewpagertransformer I/MotionEvent: C-onInterceptTouchEvent
04-14 17:36:11.767 12071-12071/com.example.viewpagertransformer I/MotionEvent: C-onTouchEvent
04-14 17:36:11.767 12071-12071/com.example.viewpagertransformer I/MotionEvent: B-onTouchEvent
04-14 17:36:11.767 12071-12071/com.example.viewpagertransformer I/MotionEvent: A-onTouchEvent
打印结果证明我们的猜测是对的。如果容器的onInterceptTouchEvent方法中返回false,不会影响事件继续向下传递。
接下来我们分析最后一个方法,onTouchEvent,还是一样,先更改A容器中onTouchEvent返回true,EventViewGroup代码修改如下:
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
Log.i("MotionEvent", TAG + "-onTouchEvent");
}
if ("A".equals(TAG)) return true;
return super.onTouchEvent(event);
}
打印结果:
04-14 17:38:53.838 14427-14427/com.example.viewpagertransformer I/MotionEvent: A-dispatchTouchEvent
04-14 17:38:53.838 14427-14427/com.example.viewpagertransformer I/MotionEvent: A-onInterceptTouchEvent
04-14 17:38:53.838 14427-14427/com.example.viewpagertransformer I/MotionEvent: B-dispatchTouchEvent
04-14 17:38:53.838 14427-14427/com.example.viewpagertransformer I/MotionEvent: B-onInterceptTouchEvent
04-14 17:38:53.839 14427-14427/com.example.viewpagertransformer I/MotionEvent: C-dispatchTouchEvent
04-14 17:38:53.839 14427-14427/com.example.viewpagertransformer I/MotionEvent: C-onInterceptTouchEvent
04-14 17:38:53.839 14427-14427/com.example.viewpagertransformer I/MotionEvent: C-onTouchEvent
04-14 17:38:53.839 14427-14427/com.example.viewpagertransformer I/MotionEvent: B-onTouchEvent
04-14 17:38:53.839 14427-14427/com.example.viewpagertransformer I/MotionEvent: A-onTouchEvent
我们看到打印流程跟正常流程一样,这是什么原因呢,我们看A容器的onTouchEvent本来就是事件传递的最后一个,所以看不出来影响,我们就更改B容器的onTouchEvent方法返回值为true,EventViewGroup代码修改如下:
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
Log.i("MotionEvent", TAG + "-onTouchEvent");
}
if ("B".equals(TAG)) return true;
return super.onTouchEvent(event);
}
打印结果:
04-14 17:40:39.423 15966-15966/com.example.viewpagertransformer I/MotionEvent: A-dispatchTouchEvent
04-14 17:40:39.423 15966-15966/com.example.viewpagertransformer I/MotionEvent: A-onInterceptTouchEvent
04-14 17:40:39.423 15966-15966/com.example.viewpagertransformer I/MotionEvent: B-dispatchTouchEvent
04-14 17:40:39.423 15966-15966/com.example.viewpagertransformer I/MotionEvent: B-onInterceptTouchEvent
04-14 17:40:39.423 15966-15966/com.example.viewpagertransformer I/MotionEvent: C-dispatchTouchEvent
04-14 17:40:39.423 15966-15966/com.example.viewpagertransformer I/MotionEvent: C-onInterceptTouchEvent
04-14 17:40:39.423 15966-15966/com.example.viewpagertransformer I/MotionEvent: C-onTouchEvent
04-14 17:40:39.423 15966-15966/com.example.viewpagertransformer I/MotionEvent: B-onTouchEvent
我们看到 ,事件传递到B的onTouchEvent方法就结束了,我们猜测, 如果onTouchEvent返回true,事件就在onTouchEvent方法中消耗了,不会继续向上传递,为了验证猜测,我们继续修改C容器的onTouchEvent方法返回true, EventViewGroup代码修改如下:
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
Log.i("MotionEvent", TAG + "-onTouchEvent");
}
if ("C".equals(TAG)) return true;
return super.onTouchEvent(event);
}
打印结果:
04-14 17:43:26.432 18377-18377/com.example.viewpagertransformer I/MotionEvent: A-dispatchTouchEvent
04-14 17:43:26.432 18377-18377/com.example.viewpagertransformer I/MotionEvent: A-onInterceptTouchEvent
04-14 17:43:26.432 18377-18377/com.example.viewpagertransformer I/MotionEvent: B-dispatchTouchEvent
04-14 17:43:26.432 18377-18377/com.example.viewpagertransformer I/MotionEvent: B-onInterceptTouchEvent
04-14 17:43:26.432 18377-18377/com.example.viewpagertransformer I/MotionEvent: C-dispatchTouchEvent
04-14 17:43:26.432 18377-18377/com.example.viewpagertransformer I/MotionEvent: C-onInterceptTouchEvent
04-14 17:43:26.432 18377-18377/com.example.viewpagertransformer I/MotionEvent: C-onTouchEvent
打印结果证实了我们的猜测,如果容器onTouchEvent返回true,事件就在onTouchEvent方法中消耗了,不会继续向上传递。
为了节省验证时间,我们直接修改C容器的onTouchEvent返回false, 看看结果,EventViewGroup代码修改如下:
04-14 17:45:05.666 19822-19822/com.example.viewpagertransformer I/MotionEvent: A-dispatchTouchEvent
04-14 17:45:05.666 19822-19822/com.example.viewpagertransformer I/MotionEvent: A-onInterceptTouchEvent
04-14 17:45:05.667 19822-19822/com.example.viewpagertransformer I/MotionEvent: B-dispatchTouchEvent
04-14 17:45:05.667 19822-19822/com.example.viewpagertransformer I/MotionEvent: B-onInterceptTouchEvent
04-14 17:45:05.667 19822-19822/com.example.viewpagertransformer I/MotionEvent: C-dispatchTouchEvent
04-14 17:45:05.667 19822-19822/com.example.viewpagertransformer I/MotionEvent: C-onInterceptTouchEvent
04-14 17:45:05.667 19822-19822/com.example.viewpagertransformer I/MotionEvent: C-onTouchEvent
04-14 17:45:05.667 19822-19822/com.example.viewpagertransformer I/MotionEvent: B-onTouchEvent
04-14 17:45:05.667 19822-19822/com.example.viewpagertransformer I/MotionEvent: A-onTouchEvent
由打印结果,我们发现 onTouchEvent的返回值是false, 不会拦截事件,事件会继续向上层容器的onTouchEvent传递。
由上述实例,我们可以综合下得出事件传播流程图如下:
好了 现在我们再来看看View类和ViewGroup类有什么区别呢,我们看到,其实ViewGroup就是多了一个onInterceptTouchEvent函数,该函数的作用,就是在返回true的时候事件不再向下传递,而是传递给容器本身的onTouchEvent函数,如果是返回false,则是继续传递给下一个容器或者控件,分析一下,我们可以看出,View这类不作为容器的控件,是没有子控件的,所以它通过dispatchTouchEvent调用父类的dispatchTouchEvent方法,其实就直接调用了onTouchEvent方法,流程图可以稍作修改如下:
好了 android事件分发的流程就总结到这里。
在实际的应用中,其实我们还有一个更便捷的方法来控制事件的传播,那就是getParent().requestDisallowInterceptTouchEvent(true/false); 具体用法代码如下:
private float oldx, oldy;
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
// 通知该容器的所有上层容器,不要拦截或者消耗事件
getParent().requestDisallowInterceptTouchEvent(true);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
float x = event.getX();
float y = event.getY();
if (Math.abs(x - oldx) > Math.abs(y - oldy)) {
// 当检测到是横向滑动事件时,通知该容器的所有上层容器,不要拦截或者消耗事件,事件由该容器本身处理
getParent().requestDisallowInterceptTouchEvent(true);
} else {
// 当检测到是竖向滑动事件,通知该容器所有上层容器,该容器不再接收本次事件序列的剩余事件,由上层容器处理
getParent().requestDisallowInterceptTouchEvent(false);
}
oldx = x;
oldy = y;
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
getParent().requestDisallowInterceptTouchEvent(false);
break;
}
return super.dispatchTouchEvent(event);
}
android事件分发流程就介绍到这里。