Android View 的事件分发机制

    了解并熟悉 View 的事件分发机制对于 Android 开发者来说是一件很重要的事情,一般来说,要分析的事件分发机制的对象就是点击事件(Motion Event)。所谓的事件分发,就是对点击事件的分发过程,当一个点击事件发生后,将点击事件分发到一个具体的 View 上。

    那么为什么需要事件的分发机制呢?因为,View 是以一个树的数据结构存在,当你点击屏幕时,触摸点下面可能是几个 View 那么,你想把点击事件交给哪个 View 处理呢?比如说发生滑动冲突时,怎么解决滑动冲突呢?这时候就需要了解事件分发机制来把把点击事件交给一个具体的 View,就可以解决这些问题。

    当一个点击事件发生后,它会首先传递到 Activity,Activity 再传递给 Window,最后 Window 再传递给顶级 View,由顶级 View 来分发事件。

    事件分发主要是由 dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent() 三个方法来共同完成。

    如果点击事件能够传递到当前 View 来,dispatchTouchEvent() 就一定会被调用,返回的结果受 onInterceptTouchEvent() 和 onTouchEvent() 影响,表示是否消耗当前事件。

    dispatchTouchEvent() 的源码比较长,就不复制到这里了。可以用下面的伪代码表示:

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

    而 onInterceptTouchEvent() 方法用来判断是否拦截事件,如果拦截了事件,在这同一个事件序列中,此方法不会再次被调用,返回的结果表示是否拦截事件。上面方法将事件分发展现得淋漓尽致,当一个点击事件传递到一个 ViewGroup,当前 ViewGroup 的 dispatchTouchEvent() 方法会执行,在方法里然后由 onInterceptTouchEvent() 决定是否拦截该事件,拦截就交给 onTouchEvent() 决定是否消耗该事件,如果返回 false,在同一个事件序列里面,当前View 都无法再接收到点击事件,最终返回结果表示是否消费该事件。如果 onInterceptTouchEvent() 决定不拦截该事件,就会把当前事件传递到 ViewGroup 的子元素中去,以此来遍历整个 View 树来分发事件。

    有一个问题就是,如果遍历完所有的子元素都没有拦截或者消耗该事件又该怎么办呢?难道就把事件抛弃了吗?这个时候还不会,Android 有责任链模式,如果遍历完所有的 View 都没有拦截事件,父容器的 onTouchEvent() 方法就会被调用,一直到 Activity。如果过了 Activity 事件还是没有被拦截,这时候事件才会被抛弃。

    有两点默认需要记住:

  1. ViewGroup 默认不拦截事件,即 onInterceptTouchEvent() 方法默认返回 false。
  2. View 默认消耗事件,即 onTouchEvent() 事件默认返回 true,除非它是不可点击的,即 clickable 和 longClickable 都为 false。

    接下来看看 Activity 中 dispatchTouchEvent() 的源码:


    Activity 会把事件交给 Window 处理,返回 true 则表明事件被消耗了,返回 false 则表明没有被处理,那么 Activity 的 onTouchEvent() 就会处理事务。

    因为 Window 是一个抽象类,所以事件分发是交给它的实现类,即 PhoneWindow。


    这里可以看到 PhoneWindow 是把事件分发交给了顶级 View,也就是 DecorView。


    它也就是调用了父类 ViewGroup 的 dispatchTouchEvent() 方法来进行事件分发。如果 ViewGroup 不拦截当前事件,就会将事件交给子元素处理。它会遍历所有子元素,然后通过两点来判断是否子元素是否能够收到点击事件:子元素是否在播放动画、点击事件坐标是否在子元素区域内。如果 ViewGroup 没有子元素或者子元素没有消耗点击事件,则点击事件就会交给 ViewGroup 处理。

    下面是 View 的 dispatchTouchEvent() 方法:



    View 因为没有子元素,所有只能自己处理事件。首先它会判断有没有设置 onTouchListener,如果 onTouchListener 中的 onTouch() 方法返回 true,就不会调用 onTouchEvent() 方法,因为 onTouchListener 比 onTouchEvent() 优先级要高,onClickListener 优先级最低。如果没有设置 onTouchListener,就会使用 onTouchEvent() 来处理事件。

    只要 View 的 CLICKABLE 和 LONG_CLICKABLE 标签有一个为 true,onTouchEvent() 都会消耗这个事件,不管是不是 DISABLE 状态。当 ACTION_UP 事件发生时,会触发 performClick() 方法,如果设置了 onClickListener ,会调用它的 onClick() 方法。


    一般来说,View 的 LONG_CLICKABLE 默认为 false,可点击的 View 的 CLICKABLE 默认为 true,不可点击的 View 的  CLICKABLE 默认为 false。但是 setOnClickListener() 和 setOnLongClickListener() 方法会分别将 CLICKABLE 和 LONG_CLICKABLE 设为 true。




学习自《Android 开发艺术探索》

    

    

    

    

猜你喜欢

转载自blog.csdn.net/young_time/article/details/80286079