Android事件的分发与拦截机制

时间:2022-01-18 22:33:16

前言

 Android为我们提供了丰富的View及ViewGroup控件,使得我们可以轻松的地完成Android应用界面的绘制,同时还可以自定义精美的View控件。绘制一个界面往往需要众多的View及ViewGroup不断嵌套,由于View可能需要与用户交互,如Button响应用户的点击,EditText响应用户的输入,而ViewGroup也可以响应事件,当多个ViewGroup和View嵌套的时候就涉及到了事件该由谁(哪个View或ViewGroup来处理)处理的问题,也就涉及到了事件分发与拦截机制。Android Frameworks给View提供了dispatchTouchEvent和onTouchEvent两个关于事件的方法,相比View,ViewGroup则多了一个onInterceptTouchEvent方法。

事件分发

要分析这三个方法的区别及用途,最直接的办法的抓取log,我们可以给ViewGroup及View添加log,如:

public class FrameLayoutA extends FrameLayout {

private static final String TAG = "event_intercept";

public FrameLayoutA(@NonNull Context context) {
super(context);
}

public FrameLayoutA(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d(TAG,"FrameLayoutA dispatchTouchEvent()");
return super.dispatchTouchEvent(ev);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d(TAG,"FrameLayoutA onInterceptTouchEvent()");
return super.onInterceptTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG,"FrameLayoutA onTouchEvent()");
return super.onTouchEvent(event);
}
}
public class FrameLayoutB extends FrameLayout {

private static final String TAG = "event_intercept";

public FrameLayoutB(@NonNull Context context) {
super(context);
}

public FrameLayoutB(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d(TAG,"FrameLayoutB dispatchTouchEvent()");
return super.dispatchTouchEvent(ev);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d(TAG,"FrameLayoutB onInterceptTouchEvent()");
return super.onInterceptTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG,"FrameLayoutB onTouchEvent()");
return super.onTouchEvent(event);
}
}
public class MyButton extends TextView {

private static final String TAG = "event_intercept";

public MyButton(Context context) {
super(context);
}

public MyButton(Context context, AttributeSet attrs) {
super(context, attrs);
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d(TAG,"MyButton dispatchTouchEvent()");
return super.dispatchTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG,"MyButton onTouchEvent()");
return super.onTouchEvent(event);
}
}

添加嵌套布局即可分析log,如:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">


<com.lt.demo.uilayout.FrameLayoutA
android:layout_width="match_parent"
android:layout_height="wrap_content">

<com.lt.demo.uilayout.FrameLayoutB
android:layout_width="match_parent"
android:layout_height="wrap_content">

<com.lt.demo.uilayout.MyButton
android:layout_width="wrap_content"
android:text="Button"
android:layout_height="wrap_content" />

</com.lt.demo.uilayout.FrameLayoutB>
</com.lt.demo.uilayout.FrameLayoutA>
</LinearLayout>

点击button,抓取的log如下:

Android事件的分发与拦截机制

如下图所示,可以看到在事件拦截机制中,事件的传递首先通过最外层的ViewGroup向内一层层传递给子布局或子view,即由外到内的传递。在事件的传递过程中系统首先回调FrameLayoutA的dispatchTouchEvent的方法(事件分发),紧接着回调了FrameLayoutA的onInterceptTouchEvent方法(事件拦截),接着就回调FrameLayoutB的dispatchTouchEvent方法,再是它的onInterceptTouchEvent方法,最后回调了最内层的子view MyButton的dispatchTouchEvent方法完成了事件的传递。而在事件的处理过程中首先由最内层view处理,回调了它onTouchEvent方法,如果这个方法返回false,则事件就会再由它的父控件处理,回调父控件的onTouchEvent方法,依次往外,直到事件处理完毕,即返回true。

Android事件的分发与拦截机制

事件拦截

 事件在传递的过程中可以被上层的ViewGroup拦截,在它的onInterceptTouchEvent方法中返回true则将拦截事件,如在FrameLayoutA的onInterceptTouchEvent返回true。

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d(TAG,"FrameLayoutA onInterceptTouchEvent()");
return true;
}

这时事件就会被FrameLayoutA给拦截下来,而不会往下传递了,log如下:

Android事件的分发与拦截机制

相应的事件传递及处理则如下图所示

Android事件的分发与拦截机制

如果让B的onInterceptTouchEvent方法返回true呢,即:

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d(TAG,"FrameLayoutB onInterceptTouchEvent()");
return true;
}

log如下

Android事件的分发与拦截机制

相应的事件传递及处理如下图

Android事件的分发与拦截机制

事件处理

 一个事件分下来,需要子view来响应它,那么事件的处理顺序通过上面的分析已经很清楚了,事件的处理是由内而外的,当子view处理完后考虑要不要交给父控件处理,依次往外。涉及到事件处理的方法就是onTouchEvent,当这个方法返回true的时候表示事件已经处理完毕,也就不会往外传递了。

这里将MyButton的onTouchEvent返回true,即

@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG,"MyButton onTouchEvent()");
return true;
}

相应的log为

Android事件的分发与拦截机制

相应的事件传递及处理图如下

Android事件的分发与拦截机制

总结

 其实Android的事件分发及拦截机制类似于现实世界任务的分配,如经理收到任务后会考虑(onInterceptTouchEvent的true或false)要不要交给(dispatchTouchEvent分发)主管,而主管收到任务后会考虑要不要分给工程师,而工程师收到任务后没得考虑,只有执行(onTouchEvent)任务了,而任务完成后往往需要向上级汇报,即工程师汇报给主管,主管汇报给经理,依次往上。当然如果工程师受不了这家公司,打算不干了,索性也可以不向上级汇报了(onTouchEvent返回true)。也有可能是主管收到任务后觉得下面的工程师处理不好,或是下面的工程师任务比较多,索性任务就自己处理(拦截)了。