Android事件的分发与拦截机制

Posted 宿罪

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android事件的分发与拦截机制相关的知识,希望对你有一定的参考价值。

前言

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如下:

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

事件拦截

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

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

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

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

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

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

log如下

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

事件处理

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

这里将MyButton的onTouchEvent返回true,即

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

相应的log为

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

总结

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

以上是关于Android事件的分发与拦截机制的主要内容,如果未能解决你的问题,请参考以下文章

Android触摸事件分发

(转)Android ViewGroup事件分发机制

Android事件分发机制

反思|Android 事件拦截机制的设计与实现

Android图解浅析事件拦截机制

Android事件分发机制浅析