Android 事件分发机制总结

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xingxtao/article/details/79600939

简述

我们知道,Android 的事件分发基本遵循 Activity —-> ViewGroup —-> View 依次从顶层至底层的顺序进行传递的,这其中主要涉及以下三个方法:

  1. public boolean dispatchTouchEvent() :
    事件分发方法,如果一个事件传递给了 View ,那么 dispatchTouchEvent()方法一定被调用。
    返回值:标识是否消费了当前事件,可能是 View 本身的 onTouchEvent() 方法消费,也可能是子 View 的 dispatchTouchEvent()方法中消费,返回 true 表示事件被消费掉,本次事件传递终止;返回 false 表示 View 以及子 View 都没有消费掉事件,将调用父 View 中的 onTouchEvent() 方法。

  2. public boolean onInterceptTouchEvent() :
    事件拦截方法,用来判断 ViewGroup 是否拦截事件,终止事件向下传递。
    返回值:是否拦截事件,返回 true 表示拦截了事件,那么事件将不再向下传递,而是调用 ViewGroup 本身的 onTouchEvent()方法; 返回 false 表示不拦截事件,事件将向下分发给子 View 的 dispatchTouchEvent() 方法。

  3. public boolean onTouchEvent() :
    事件消费方法,在 dispatchTouchEvent() 方法中进行调用。
    返回值:返回 true 表示事件被消费掉了,本次事件终止; 返回 false 表示事件没有被消费掉,将调用父 View 的 onTouchEvent() 方法。
事件方法 Activity ViewGroup View
boolean dispatchTouchEvent(MotionEvent ev)
boolean onInterceptTouchEvent(MotionEvent ev) 没有 没有
boolean onTouchEvent(MotionEvent ev)

从上表可以看出:只有在 ViewGroup 中才存在 onInterceptTouchEvent() 方法,这三个方法的返回值,返回为 true 表示事件被消费了,事件停止传递;返回 false,事件继续传递。

事件类型

常用的事件类型就三种:

  1. ACTION_DOWN (按下)
  2. ACTION_MOVE (移动)
  3. ACTION_UP (抬起)

事件的传递在我们手指按下 (ACTION_DOWN) 的瞬间就发生了,如果手指有移动就会触发若干个移动事(ACTION_MOVE),当手指抬起时就会触发抬起事件(ACTION_UP),这样就组成了一个事件序列,即一个事件序列可以表示为: ACTION_DOWN —> ACTION_MOVE …. ,ACTION_,MOVE —> ACTION_UP。


既然事件分发是从 Activity —> ViewGroup —> View 依次传递的,那么我们就先从 Activity 进行分析。

Activity 事件分发过程

当事件产生后,事件首先会传递给当前 Activity,会调用 Activity 的 dispatchTouchEvent() 方法,看下 Activity 源码中的处理过程。
这里写图片描述

由于我们一般产生的事件都是 MotionEvent_ACTION_DOWN 开始的,所以上面代码一般都会调用到 onUserInteraction() 这个方法,我们看下 onUserInteraction() 这个方法做了哪些事情。
这里写图片描述

可以看到,这个 onUserInteraction() 方法的实现是空的,通过注释了解到,该方法主要作用是实现 屏保,并且当次 Activity 在栈顶的时候,点击 Home,Back,Recent 键都会触发这个方法,再来看下 Activity dispatchTouchEvent() 方法中第二个 if 判断语句:getWindow().superDispatchTouchEvent(),getWindow() 获取是 Window,由于 Window 是一个抽象类,所以到其唯一实现类 PhoneWindow 看下 PhoneWindow.superDispatchTouchEvent() 方法做了什么事情。

honewindo

可以看出,直接调用了 DecorView 的 superDispatchTouchEvent(),DecorView 是继承自 FrameLayout ,作为顶层 View,是所有界面的父布局,而 FrameLayout 作为 ViewGroup 的子类,所以直接调用了 ViewGroup 的 dispatchTouchEvent() 方法,即事件从 Activitiy 传递给了 ViewGroup 。

ViewGroup 事件分发过程

ViewGroup 事件分发过程可以如下伪代码表示:

    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean consume = false;
        if(onInterceptTouchEvent(ev)) {
            consume  = onTouchEvent(ev);
        } else {
            consume = child.dispatchTouchEvent(ev);
        }
        return consume;
    }

当事件传递给当前 ViewGroup 时,首先会调用它的 dispatchTouchEvent() 方法,如果这个 ViewGroup 的 onInterceptTouchEvent() 方法 返回 true 表示它要拦截当前事件,那么事件就会交给该 ViewGroup 处理,即它的 onTouchEvent() 方法会被调用; 如果这个 ViewGroup 的 onInterceptTouchEvent() 方法返回 false ,就表示该 ViewGroup 不拦截当前事件,这时事件就会继续传递给它的子 view, 接着子 view 的 dispatchTouchEvent() 方法就会被调用,如此依次传递,直至事件被处理。

这里写图片描述

案例分析

该案例,Activity 中放一个自定义的 LinearLayout , LinearLayout 中放一个自定义的 View.

自定义 LinearLayout 布局:

public class MyLinearLayout extends LinearLayout {

    private static final String TAG = "debug";


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

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

    public MyLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

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

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

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


}

自定义 view


public class MyView extends View {

    private static final String TAG = "debug";


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

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

    public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.d(TAG, "View  ---  dispatchTouchEvent: ");
        return super.dispatchTouchEvent(event);
    }

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

Activity 布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="com.example.administrator.myapplication.MainActivity">

    <com.example.administrator.myapplication.MyLinearLayout
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:background="#ccc"
        android:gravity="center">


        <com.example.administrator.myapplication.MyView
            android:layout_width="150dp"
            android:layout_height="150dp"
            android:background="#72c9e6" />

    </com.example.administrator.myapplication.MyLinearLayout>

</LinearLayout>
public class MainActivity extends AppCompatActivity {

    private static final String TAG = "debug";

    @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: ");
        return super.dispatchTouchEvent(ev);

    }

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

}

emo-scree

我们点击中间 绿色的view,看到的日志如下:

emo-clic

分析:我们知道一次事件(比如 ACTION_DOWN)是由上向下进行传递的,也就是依次传递给 Activity,ViewGroup , View。按下的瞬间 ACTION_DOWN 触发,Activity 的 dispatchTouchEvent() 被先被调用,即打印出了第一行日志,接着事件传递给 MyLinearLayout ,此时 MyLinearLayout 的 dispatchTouchEvent() 方法被调用,即打印出第二行日志。

注意:上面的事件分发流程图中,ViewGroup 的 dispatchTouchEvent() 方法会调用自身的 onInterceptTouchEvent() 方法,如果 ViewGroup 的 onInterceptTouchEvent() 方法返回 true ,表示要拦截该事件,事件将会交给该 ViewGroup 自身进行处理,即会调用 ViewGroup 的 onTouchEvent() 方法,那么它的子 View 的 dispatchTouchEvent() 就不会执行了;如果 ViewGroup 的 onInterceptTouchEvent() 方法返回 false ,表示不拦截该事件,事件继续向下传递,会调用子 View 的 dispatchTouchEvent() 方法。

在我们案例中,MyLinearLayout 中的 onInterceptTouchEvent() 调用了 ViewGroup 的 onInterceptTouchEvent() 方法,由于 ViewGroup 默认是不拦截事件的,所以事件传递给了 MyView,即 MyView 的 dispatchTouchEvent() 方法会被调用,即打印出了第四行日志。View 没有拦截事件方法,调用了自己的 onTouchEvent() 方法,即打印出了第五行日志。MyView onTouchEvent() 返回默认值 false,表示没有消费事件,事件依次向上回传,会调用 MyLinearLayout 的 onTouchEvent() 方法,即打印出第六行日志,MyLinearLayout 中调用了 ViewGroup 的 onTouchEvent() 方法,而 ViewGroup 实际是继承了 View 的 onTouchEvent() 方法,所以 MyLinearLayout 的 onTouchEvent() 方法返回了 false, 此时事件回传给了 Activity 的 onTouchEvent() 方法,即打印出了第七行日志,ACTION_DOWN 事件结束。

最后两行日志是怎么产生的呢?这是由于 ACTION_UP 事件产生的(点击的时候没有移动手指,所以没有 ACTION_MOVE 事件),那为什么只有 Activity 产生了 ACTION_UP 事件,而 MyLinearLayout ,MyView 没有接受到呢?这是因为对于一个 View 来说,如果一个事件序列中的 ACTION_DOWN 你没有消费掉,那么该事件序列中后续的 ACTION_MOVE, ACTION_UP 就不会传递给你了,所以没有打印出 MyLinearLayout ,MyView 的日志。那为什么 Activity 的 onTouchEvent() 返回 false 了,还能接收到 ACTION_UP 事件呢?这是因为一个完整的事件序列总是从 ACTION_DOWN 开始,以 ACTION_UP 结束。

(1) 我们修改下 MyView 的 onTouchEvent() ,使其返回值返回 true。 按照上面分析的思路,MyView 的 onTouchView() 返回 true ,表示消费事件,事件在 MyView 上终止传递,不会回传给 父布局,同时,事件序列中后续事件 ACTION_MOVE, ACTION_UP 也会传递给 MyView。

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        StringBuffer sb = new StringBuffer("View  ---  onTouchEvent: ");
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                sb.append("--- ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                sb.append("--- ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                sb.append("--- ACTION_UP");
                break;
        }
        Log.d(TAG, sb.toString());
        return true;   // 这里将 MyView 的 onTouchEvent() 返回值改成 true
    }

iew-action_u

(2) 我们将 MyView 的 dispatchTouchEvent() 返回值也改成 true ,看下什么效果?

 @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        StringBuffer sb = new StringBuffer("View  ---  dispatchTouchEvent: ");
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                sb.append("--- ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                sb.append("--- ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                sb.append("--- ACTION_UP");
                break;
        }
        Log.d(TAG, sb.toString());
//        return super.dispatchTouchEvent(event);  // 即不会执行View类中的 dispatchTouchEvent()
        return true;
    }

iew-dispatch-tru

dipatchTouchEvent() 返回 true ,消费了事件,后续的 ACTION_MOVE,ACTION_UP 事件会传递给 MyView , 由于 MyView 中的 onTouchEvent() 是在其父类(父类即是 View) View 的 dispatchTouchEvent() 方法中调用的,现在我们把 MyView 中的 dispatchTouchEvent() 返回值直接改成 true ,而不是 super.dispatchTouchEvent(),即 View 类中的 dispatchTouchEvent() 没有被调用,从而 MyView 中的 onTouchEvent() 也就没有调用,所以没有打印 MyView 的 onTouchEvent() 方法的日志。

(3) MyView 的 dispatchTouchEvent() 返回值改成 false, 结果会怎样呢?

@Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        StringBuffer sb = new StringBuffer("View  ---  dispatchTouchEvent: ");
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                sb.append("--- ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                sb.append("--- ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                sb.append("--- ACTION_UP");
                break;
        }
        Log.d(TAG, sb.toString());
//        return super.dispatchTouchEvent(event);
        return false;
    }

iew-dispatch-fals

MyView 的 dispatchTouchEvent() 方法返回 false,表示不消费事件,ACTION_DOWN 事件回传给父容器 MyLinearLayout ,后续的事件 ACTION_MOVE ,ACTION_UP 不会传递到 MyView 上,同时 MyLinearLayout 也没有消费事件,后续事件也不会传递到 MyLinearLayout 中, 直接在 Activity 中的终止。

(4) 我们把 MyLinearLayout 的 onInterceptTouchEvent() 返回值改成 true ,结果是什么样呢?

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        StringBuffer sb = new StringBuffer("LinearLayout --- onInterceptTouchEvent: ");
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                sb.append("--- ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                sb.append("--- ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                sb.append("--- ACTION_UP");
                break;
        }
        Log.d(TAG, sb.toString());
        return true;
    }



    @Override
    public boolean onTouchEvent(MotionEvent event) {
        StringBuffer sb = new StringBuffer("LinearLayout --- onTouchEvent: ");
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                sb.append("--- ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                sb.append("--- ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                sb.append("--- ACTION_UP");
                break;
        }
        Log.d(TAG, sb.toString());
        return super.onTouchEvent(event);
    }

inearlayout-intercept-tru

MyLinearLayout 的 onInterceptTouchEvent() 返回 true,表示拦截事件,ACTION_DOWN 事件会交给自身的 onTouchEvent() 处理,不会传递给 MyView 的 dispatchTouchEvent(),由于 MyLinearLayout 的 onTouchEvent() 方法没有返回 true ,即 没有消费掉 ACTION_DOWN 事件,所以 ACTION_DOWN 事件会回传给 Activity 的 onTouchEvent() ,后续的 ACTION_MOVE ,ACTION_UP 事件不会传递给 MyLinearLayout 中。

(5) 我们把 MyLinearLayout 中的 onTouchEvent() 方法返回值改成 true ,看下什么结果?

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        StringBuffer sb = new StringBuffer("LinearLayout --- onInterceptTouchEvent: ");
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                sb.append("--- ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                sb.append("--- ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                sb.append("--- ACTION_UP");
                break;
        }
        Log.d(TAG, sb.toString());
        return true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        StringBuffer sb = new StringBuffer("LinearLayout --- onTouchEvent: ");
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                sb.append("--- ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                sb.append("--- ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                sb.append("--- ACTION_UP");
                break;
        }
        Log.d(TAG, sb.toString());
//        return super.onTouchEvent(event);
        return true;
    }

inearlayout-ontouchevent-tru

MyLinearLayout 的 onInterceptTouchEvent() , onTouchEvent() 方法都返回 true ,表示拦截,并消费事件,拦截事件即表示事件会在 MyLinearLayout 的 onTouchEvent() 方法进行处理,不会再继续传递,同时后续的 ACTION_MOVE ,ACTION_UP 事件也会传递给 MyLinearLayout 中。

(6) 我们把 MyLinearLayout 的 dispatchTouchEvent() 返回值改成 true,结果是怎样呢?

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        StringBuffer sb = new StringBuffer("LinearLayout --- dispatchTouchEvent: ");
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                sb.append("--- ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                sb.append("--- ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                sb.append("--- ACTION_UP");
                break;
        }
        Log.d(TAG, sb.toString());
//        return super.dispatchTouchEvent(ev);
        return true;
    }

inearlayout-dispatch-tru

MyLinearLayout 的 dispatchTouchEvent() 返回 true ,表示消费掉了事件,事件不会向下传递,同时 后续的事件 ACTION_MOVE,ACTION_UP 还会传递给 MyLinearLayout 中。

猜你喜欢

转载自blog.csdn.net/xingxtao/article/details/79600939
今日推荐