/(ㄒoㄒ)/~~,被Android中的事件传递困扰好久了,一直以来都是云里雾里的,今天抽一下午的时间从头到尾梳理,琢磨一下Android的事件传递机制以此博客进行记录,如有错误还请指正(●’◡’●)

我们知道Android 中的View结构是树形结构,View可以放在ViewGroup中,而ViewGroup也可以放在ViewGroup当中,这样一层层的嵌套,那么问题来了,我们的触摸事件只有一个,而View可以多层嵌套,那么这个触摸事件是怎么进行传递的呢?

在研究Android的具体View之前,我们先了解一下Activity的UI框架

android 事件参数传递 android 事件传递原理_事件传递机制

当我们的手指点击到屏幕事件的传递依次是:

android 事件参数传递 android 事件传递原理_android_02

大致的了解一下以上的内容之后,我们下面开始研究ContentView中,也就是我们平常开发中经常涉及到的View的事件传递的过程:

touch事件传递的相关方法:

1.dispatchTouchEvent(MotionEvent ev) ==> 进行事件分发

2.onInterceptTouchEvent(MotionEvent ev)==>进行事件的拦截

3.onTouchEvent(MotionEvent event)==>进行事件的响应

Demo布局:

android 事件参数传递 android 事件传递原理_滑动冲突_03

这里的ViewGroup重写了以上三个方法
注:由于MyView不是ViewGroup的View,没有自己的子View因此不要进行事件的拦截,所以只重写了1,3方法

运行Demo点击MyView(最内部的View)此时控制台打印的Log:

android 事件参数传递 android 事件传递原理_事件传递机制_04

从打印的Log我们可以很清楚的看到:

  • 事件传递的过程中:touch事件的传递是先由外部的View接受,然后传递给其内层的View。此时事件传递完成
  • 事件处理的过程中:touch事件的传递是从最内部的View开始,依次传递给最外部的View(如果一直没有将事件消费掉

大致的了解了Android中的事件传递下面详细的记录一下以上列出的三种方法的使用:

  • 事件分发:public boolean dispatchTouchEvent(MotionEvent ev):
    不管是Activity还是View,事件分发的自身也具有消费的能力。如果dispatchTouchEvent返回true则表示该事件在本层不再进行分发,且已经在事件分发自身中被消费了,如上面的内容所提及的。如果dispatchTouchEvent返回false,表示事件在本层不再继续进行分发,而是交给上层的View的onTouchEvent方法进行消费。dispatchTouchEvent的默认返回super.dispatchTouchEvent(ev)此时事件交给本层的onInterceptTouchEvent(MotionEvent ev)方法进行处理。
    MyGroupB 的dispatchTouchEvent方法返回true时的Log打印:

MyGroupB 的dispatchTouchEvent方法返回false时的Log打印:

android 事件参数传递 android 事件传递原理_android_05

  • 事件的拦截:public boolean onInterceptTouchEvent(MotionEvent ev):如果onInterceptTouchEvent返回true,则将当前事件进行拦截并交由本层View的onTouchEvent方法处理。如果onInterceptTouchEvent返回false,则将当前事件交给子View的onTouchEvent方法处理。 super.onInterceptTouchEvent(ev)和返回false时的处理逻辑相同。
    MyGroupB 的onInterceptTouchEven方法返回true时的Log打印:

MyGroupB 的onInterceptTouchEven方法返回false时和之前运行结果一样,此处不再赘述

  • 事件响应:public boolean onTouchEvent(MotionEvent ev):

如果onTouchEvent方法返回true,则表示onTouchEvent处理完相关逻辑之后并消费掉了当前的事件。表示此次的touch事件处理结束,将不会再向上传递如果onTouchEvent的方法返回false则表示不对当前的touch事件进行处理和消费,继续将事件向上层的View传递。如果返回super.onTouchEvent(event)则与返回false时的逻辑相同。

MyGroupB 的onTouchEvent方法返回true时的Log打印:

android 事件参数传递 android 事件传递原理_android 事件参数传递_06

MyGroupB 的onTouchEvent方法返回false时和首次运行的结果的Log打印一致,此处不再赘述:

下面用一张图概括上面的流程(一般情况下,不会修改dispatchTouchEvent()方法,下图省略掉事件分发这步):

android 事件参数传递 android 事件传递原理_android_07

(图画的太丑了/(ㄒoㄒ)/~~)

总结:dispatchTouchEvent()方法无论时返回true还是false,事件都不会继续向下分发,只要当它返回super.dispatchTouchEvent(ev)才能将事件继续向下分发事件是否能够成功执行还onInterceptTouchEvent()方法来确保事件是否被拦截

下面通过一个Demo通过上面提到的一些方法来解决一些真实开发中遇到的滑动冲突的事件:

ViewPager嵌套ListView产生的滑动冲突:

android 事件参数传递 android 事件传递原理_android_08


可以看到滑动包含listView的ViewPager时此时左右滑动时不能切换ViewPager的。此时就可以通过前面提到的一些方法进行解决:判断手指滑动的方向,如果是左右滑动就拦截touch事件不让touch事件继续向下传递,如果时上下滑动就不进行事件的拦截让其处理。

重写onInterceptTouchEvent(MotionEvent ev)方法:

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {

        Log.i("wk", "ViewPager_Fragment==>onInterceptTouchEvent");


        switch (ev.getAction()) {


            case MotionEvent.ACTION_DOWN:

                lastX = (int) ev.getRawX();
                lastY = (int) ev.getRawY();
                break;

            case MotionEvent.ACTION_MOVE:

                offsetX = (int) ev.getRawX() - lastX;
                offsetY = (int) ev.getRawY() - lastY;
                //判读滑动的方向
                if (Math.abs(offsetX) > Math.abs(offsetY)) {

                    Log.i("wk1", "左右滑动");

                    return true;

                } else {

                    Log.i("wk1", "上下滑动");

                    return false;
                }

            case MotionEvent.ACTION_UP:

                break;

        }

        return super.onInterceptTouchEvent(ev);
    }

解决后:

android 事件参数传递 android 事件传递原理_滑动冲突_09

对于外部控件被内部的控件影响的时候,此时可以使用此方法从外部进行拦截。

还有一种就是内部的控件被外部的控件影响而产生
滑动冲突的时候,此时可以调用getParent().requestDisallowInterceptTouchEvent(true)()
让父容器不拦截子View的事件,从内部进行事件的拦截。

(⊙﹏⊙),可能只有我自己才能理解上面这段话O(∩_∩)O,晕(((φ(◎ロ◎;)φ)))

通过写这篇博客,更加深入的理解了android中的事件传递机制了。哈哈 有种腾云驾雾的感觉,就当前来说写的最长时间的一篇博客,请大家指正里面出现的错误。