从Log中看View的事件分发机制

怕什么真理无穷 进一寸有进一寸的欢喜

在实际项目的开发中,View的事件分发机制起到了很重要的作用,尤其是遇到不按常理出牌的产品经理的时候,没事给你一个嵌套,没事再给你加个嵌套,那么事件分发就显得尤为重要。对于经常打交道的滑动冲突,了解了View事件分发的原理,也就可以很容易的解决。实践出真知,我们从一个Demo出发,通过Log的形式分析View是如何将事件分发的,又是如何拦截并作出相应处理的。

一、创建Demo

首先,创建一个简单的Demo,自定义最外层的ViewGroup,命名为CustomViewGroup1,重写onInterceptTouchEvent,onTouchEvent,dispatchTouchEvent三个方法,并将背景设为红色。

public class CustomViewGroup1 extends FrameLayout {
    private static final String TAG = "CustomeViewGroup1";

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

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

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

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

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

和上述代码一样,自定义中间层的CustomViewGroup2,重写拦截和处理的方法,将背景设为蓝色。
接着自定义View,重写onTouchEvent和dispatchTouchEvent方法,将背景设置为黄色。

public class CustomView extends Button {
    private static final String TAG = "CustomView";

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

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

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

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

最后运行的效果为
从Log中看View的事件分发机制_第1张图片
Custom.png

红色为最外层CustomViewGroup1,蓝色为中间层CustomViewGroup2,黄色为CustomView。

二、分析

2.1

从上面自定义ViewGroup的代码可以看出,View的事件分发涉及到了几个重要的方法,磨刀不误砍柴工,下面就先来分析一下这几个方法。

  • boolean dispatchTouchEvent(MotionEvent event):事件分发
  • boolean onInterceptTouchEvent(MotionEvent ev):事件拦截
  • boolean onTouchEvent(MotionEvent event):事件处理

dispatchTouchEvent为事件分发的第一步,将事件向下分发;onInterceptTouchEvent表示是否拦截当前事件,当返回true时,表示拦截,也就是下面的事件就不会触发;onTouchEvent表示处理事件,返回结果表示是否消耗当前事件。这里不清楚没关系,接着看下面的分析。

上述三种方法默认返回false,不拦截也不消耗事件

2.2

接下来就通过Log一步一步的看事件分发的过程。当点击黄色界面,也就是最底层的View时,Log如下:

从Log中看View的事件分发机制_第2张图片
Log1.png

从上面的Log可以看出, 在默认的情况下,最先触发CustomeViewGroup1(红色界面)的dispatchTouchEvent和onInterceptTouchEvent方法,接着触发CustomViewGroup2(蓝色界面)的dispatchTouchEvent和onInterceptTouchEvent方法,最后才触发CustomView(黄色界面)的dispatchTouchEvent和onTouchEvent,一级一级向下传递。
上述为默认情况,不拦截也不消耗任何事件,事件就会一层一层的向下传递,那么当我们在第一层CustomeViewGroup1的onInterceptTouchEvent中返回true,将点击事件拦截下来,会出现什么情况呢?

public class CustomViewGroup1 extends FrameLayout {
...
 @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.d(TAG, "onInterceptTouchEvent:" );
        return true;
    }
}

同样点击黄色区域,再看Log:


Log2.png

这时我们发现,Log上只走了CustomeViewGroup1的方法,并且走了onTouchEvent,这就说明CustomeViewGroup1将事件拦截了下来,并没有分发下去,自己就将事件进行了处理。那么我们再试着将CustomeViewGroup2的onInterceptTouchEvent返回true,依据上面的分析我们猜测到CustomeViewGroup2就停止传递,接着会触发onTouchEvent。同样我们修改代码并点击VIew黄色区域。

public class CustomViewGroup2 extends FrameLayout {
...
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.d(TAG, "onInterceptTouchEvent: ");
        return true;
    }
}
从Log中看View的事件分发机制_第3张图片
Log3.png

从上述Log可以看出我们的猜测是正确的,在CustomeViewGroup2进行拦截,事件在分发到CustomeViewGroup2就没有继续向下分发了。不过我们又发现Log的最后两行,先触发CustomViewGroup2的onTouchEvent接着触发CustomViewGroup1的 onTouchEvent,这是属于onTouchEvent返回false,CustomViewGroup2没有消耗掉当前事件,导致将事件返回给了他的上一层处理。

2.3、小结

从上面的Log分析我们可以得出三种不同情况下的事件分发规则:
1、在正常情况下,三个方法的返回值都为false,不拦截也不消耗事件,那么事件就会从ViewGroup1传递到ViewGroup2再传递到最后的View。
2、当ViewGroup1的onInterceptTouchEvent方法返回true,那么ViewGroup1就拦截下了这个事件,后面的ViewGroup2和View就接受不到任何事件的信号,并调用ViewGroup1的onTouchEvent方法对事件进行处理。
3、当ViewGroup2的onInterceptTouchEvent方法返回true,那么ViewGroup2拦截下当前事件,并调用自己的onTouchEvent方法,若ViewGroup2的onTouchEvent返回false,表示不消耗这个事件,那么就会返回他的上一层,将这个事件交给上一层处理,也就是ViewGroup1的onTouchEvent方法会被调用。相反,ViewGroup2的onTouchEvent返回true,就不会返回给上一层。

简单的打个比方,ViewGroup1代表校长,ViewGroup2代表老师,View则代表学生
1、正常情况下是校长将一个任务首先下发给老师,接着老师下发给学生,学生收到任务,并执行该任务。
2、第二种情况,校长发现该任务自己可以处理就不用下发给老师,及校长的onInterceptTouchEvent返回true,将这个任务拦截下来了。后面的老师和学生也就不知道该任务是什么。
3、第三种情况,校长将任务下发给老师,老师却将任务拦截了下来,并没有告诉学生,即ViewGroup2的onInterceptTouchEvent方法返回true。当老师准备自己处理的时候发现这个任务太难,自己解决不了,就onTouchEvent方法返回false,将事情又返回给更高一级的校长处理。

三、onTouchEvent

接下来分析onTouchEvent中是如何处理动作的,我们在onTouchEvent的方法中加上三种最常见的动作的Case,ACTION_DOWN手指点击,ACTION_MOVE手指滑动,ACTION_UP手指抬起。

   @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d(TAG, "onTouchEvent:");
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.d(TAG, "onTouchEvent: ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.d(TAG, "onTouchEvent: ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.d(TAG, "onTouchEvent: ACTION_UP");
                break;
        }
        return super.onTouchEvent(event);
    }

点击画面并滑动,


return false.png

我们可以看到日志Log上只有ACTION_DOWN事件发生,并没有触发滑动MOVE和手指抬起的UP事件,这是因为ACTION_DOWN事件默认是返回false,表示不消耗DOWN事件,也就是说DOWN动作发生后是不会向下传递,也不会触发MOVE和UP的动作。所以我们需要触发后面的动作时,需要将返回值设为 true,表示消耗当前事件。

   @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d(TAG, "onTouchEvent:");
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.d(TAG, "onTouchEvent: ACTION_DOWN");
                return true;
            case MotionEvent.ACTION_MOVE:
                Log.d(TAG, "onTouchEvent: ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.d(TAG, "onTouchEvent: ACTION_UP");
                break;
        }
        return super.onTouchEvent(event);
    }

这时我们同样去点击和滑动画面,


从Log中看View的事件分发机制_第4张图片
return true.png

可以从上图看到,MOVE和UP动作都相应触发。

四、最后

至此,View的事件分发基本上从头捋了一遍,更多的源码的细节准备好好研究一下,在后面的文章中展示出来。其实将事件分发的文章网络上比比皆是,本没有想法去记录这篇文章,只是因为前几天在做Android测试题的时候,有涉及到事件分发,当时将onInterceptTouchEvent和onTouchEvent的返回值弄反。原本以为自己记得很清楚,到了用的时候却弄混,才发现有些东西需要自己去好好的写上一遍,才会真正明白,也记得更牢。古人云:好记性不如烂笔头,不是没有道理的。


文章同步个人博客:https://fuusy.github.io
公众号:小猿说,欢迎关注,分享Android知识。

小猿说.jpg

你可能感兴趣的:(从Log中看View的事件分发机制)