Android中View的事件分发机制

见过太多的讲解View事件分发机制的博客和文章,绝大多数都是从源码层次讲解,枯燥乏味又让初学者望而生畏,而且不方便记忆。
今天在月的任玉刚先生的《Android开发艺术探索》一书中的有关View的事件分发机制章节,伪代码的讲解形式顿时让人对”View的事件分发机制”有醍醐灌顶之神效,故将该章节的内容摘抄给大家供参考。

1.点击事件的传递规则
所谓的点击事件的事件分发,其实就是对MotionEvent事件的分发过程,即当一个MotionEvent产生了以后,系统需要把这个事件传递给一个具体的View,而这个传递的过程就是分发过程。点击事件的分发过程由三个很重要的方法共同完成:dispatchTouchEvent,onInterceptTouchEvent和onTouchEvent,下面我们先介绍一个这几个方法

1.1 public boolean dispatchTouchEvent(MotionEvent ev)
用来进行事件的分发。如果事件能够传递给当前View,那么此方法一定会被调用,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响,表示是否消耗当前事件。

1.2 public boolean onInterceptTouchEvent(MotionEvent ev)
在上述方法内部调用,用于判断是否拦截某个事件,如果当前View拦截了这个事件,那么在同一个事件序列当中,此方法不会再被调用,返回结果表示是否拦截当前事件。

1.3 public boolean onTouchEvent(MotionEvent ev)
在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接收到事件。

上述三个方法到底有什么区别?它们之间是什么关系?
用以下伪代码简单表示三者之间的关系!

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,就表示他不拦截当前事件,这时当前事件就会传递给它的子元素,接着子元素的dispatchTouchEvent方法就会被调用,如此反复直到事件被最终处理。

当一个View需要处理事件时,如果它设置了onTouchListener,那么onTouchListener中的onTOuch方法就会被调用。这时事件如何处理还要看onTouch的返回值,如果返回false,则当前View的onTouchEvent方法会被调用;如果返回true,那么onTouchEvent方法将不会被调用。由此可见,给View设置的onTouchListener,其优先级比onTouchEvent要高。在onTouchEvent方法中,如果当前设置的有onClickListener,那么他的onClick方法会被调用。可以看出,平时我们常用的onClickListener,其优先级最低,即处于事件传递的尾端。

当一个点击事件产生后,它的传递过程遵循如顺序:Activity——>Window——>View,即事件总是先传递给Activity,Activity再传递给Window,最后WIndow再传递给顶级View,顶级View接收到事件后,就会按照事件分发机制去分发事件。如果一个View的onTouchEvent返回false,那么它的父容器的onTouchEvent就会被调用,以此类推,如果所有的元素都不处理这个事件,那么这个事件就会被最终传递给Activity处理,即Activity的onTouchEvent就会被调用。

关于事件传递的机制,这里给出以下一些结论:
(1) 同一个事件序列是从手指接触屏幕的那一刻起,到手指离开屏幕的那一刻结束,在这个过程中所产生的一系列事件,这个事件序列以DOWN开始,中间含有数量不定的MOVE,最终以UP事件结束。

(2)正常情况下,一个事件序列只能被一个View拦截并消耗。这一条的原因可以参考(3),因为一旦一个元素拦截了某个事件,那么同一个事件序列内的所有事件都会直接交由它处理,因此同一个事件序列中的事件不能分别由两个View同时处理,但是通过特殊手段可以做到,比如一个View将本该自己处理的事件通过onTouchEvent强行传递给其他View处理。

(3)某个View一旦决定拦截,那么这一个事件序列都只能由他来处理(如果事件序列能够传递给他的话),并且它的onInterceptTouchEvent方法不会被调用。就是说当一个View决定拦截一个事件后,那么系统会把同一个事件序列中的其他方法都直接交给他处理,因此就不会再调用这个View的onInterceptTouchEvent方法去询问它是否要拦截了。

(4)某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false),那么同一个事件序列中的其他事件都不会再交给他来处理,并且事件将重新交由它的父元素去处理,即父元素的onTouchEvent会被调用。

(5)如果View不消耗除ACTION_DOWN以外的其他事件,那么这个点击事件就会消失,此时父元素的onTouchEvent就不会被调用,并且当前View可以持续受到后续的事件,最终这些消失的点击事件会传递给Activity处理。

(6)ViewGroup默认不拦截任何事件。

(7)View没有onInterceptTouchEvent方法,一旦有点击事件传递给他,那么它的onTouchEvent方法就会被调用。

(8)View的onTouchEvent默认都会消耗事件(返回true),除非他是不可点击的(clickable和longClickable同时为false)。

(9)View的enable属性不影响onTouchEvent的默认返回值。哪怕一个View是disable状态,只要它的clickable和longClickable有一个为true,那么它的onTouchEvent就会返回true.

(10)onClick会产生的前提是当前View是可点击的,并且它收到了DOWN和UP事件。

(11)事件传递过程是由外向内的,即事件总是先传递给父元素,然后再有父元素传递给View,通过requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素的事件分发过程,但是ACTION_DOWN事件除外。

以上是任玉刚先生有关事件分发的部分章节,如果有兴趣大家可以考虑购买以下《Android开发艺术探索》这本书,内容真心不错!

你可能感兴趣的:(android,事件分发,事件拦截,开发艺术探索)