Android事件分发机制

View的事件分发机制是解决滑动冲突的基础。
首先要理解两个概念,ViewViewGroup,控件如ButtonTexiViewImageView等都继承自View父类,布局如LinerLayoutRelativeLayout等都继承自ViewGroup。那怎么理解这两个东西呢?我们可以将整个Activity页面看做一个容器,所有的控件(View)都装在这个容器(ViewGroup)里。

首先我们先说下结论,Android中Touch事件的传递绝对都是先到ViewGroup,然后在到View,所以无论点击了哪个控件都会先调用所在布局的dispatchTouchEvent方法,
如果没有被消费掉就会向下传递到控件的dispatchTouchEvent方法。

然后我们看以下三个核心方法:

  • public boolean dispatchTouchEvent(MotionEvent ev)
    用来进行事件的分发。如果事件能够传递给当前的View,那么这个方法就一定会被调用,返回结果受当前View的onTouchEvent和下级的dispatchTouchEvent方法影响,表示是否消耗当前事件。
  • public boolean onInterceptTouchEvent(MotionEvent event)
    在dispatchTouchEvent方法内部调用,这个方法表示是否拦截某个事件,这个方法存在于父级的View中,也就是一般重写在ViewGroup中
  • public boolean onTouchEvent(MotionEvent event)
    在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件

用一段伪代码来表示三种方法的关系:

1
2
3
4
5
6
7
8
9
public boolean dispatchTouchEvent(MotionEvent ev){
boolean comsume = false;
if(onInterceptTouchEvent(ev)){
consume = onTouchEvent(ev);
} else {
consume = child.dispatchTouchEvent(ev);
}
return consume;
}

当一个,View需要处理事件时,如果它设置了OnTouchListener,那么OnTouchListener中的onTouch方法被回调。这时事件如何处理还要看onTouch的返回值,如果返回false,则当前ViewonTouchEvent方法会被调用;如果返回true,那么onTouchEvent方法将不会调用。由此可见,给View设置的OnTouchListener,其优先级比onTouchEvent要高。在onTouchEvent方法中,如果当前设置的有onClickListener,那么它的click方法会被调用,可以看出我们平时所用的onClickListener优先级最低,位于末端。
当一个点击事件产生后,它的传递过程遵循如下规则:Activity->Window->View,这里的View是顶级View,顶级View接收到事件后,会按照事件分发机制一步步向下分发事件。考虑一种情况,如果一个View的onTouchEvent返回false,那么它的父容器的onTouchEvent将会被调用,以此类推。如果所有的元素都不处理这个事件,那么最终这个事件会被传递给Activity处理,调用ActivityonTouchEvent方法。
关于事件分发机制,这里有一些结论:
(1)同一事件序列是指从手指接触屏幕的那一刻起,到手指离开屏幕,事件序列会经历down事件,以及数量不定的move事件,最后以up事件结束;
(2)正常情况下,一个事件序列只能被一个View拦截消耗;
(3)某个View一旦决定拦截,那么这一个事件序列只能由它自己处理,并且它的onInterceptTouchEvent方法
不会再被调用
(4)某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false),那么同一事件序列的其他事件都不会再交给它处理,并且事件将重新交由它的父元素去处理,即调用父元素的onTouchEvent
(5)如果View不消耗除了ACTION_DOWN的其他事件,那么这个点击事件会消失,此时父元素的onTouchEvent并不会被调用,并且当前View可以持续收到后续事件。并且这些消失的点击事件会交给Activity处理。
(6)ViewGroup默认不会拦截任何事件。
(7)View没有onInterceptTouchEvent方法,一旦有事件传递给它就会调用它的onTouchEvent方法。
(8)ViewonTouchEvent默认都会消耗事件,除非它是不可点击的(clickablelongClickable同时为falseViewlongClickable默认为false)。
(9)Viewenable属性不影响onTouchEvent的默认返回值。
(10)onClick会发生的前提是当前View是可点击的,并且它收到了down和up事件。
(11)事件传递过程都是由外向内的,通过requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素的事件分发过程,但是ACTION_DOWN事件除外。
mInputEventConsistencyVerifier变量是调试用的,不用理会。
onFilterTouchEventForSecurity()表示是否要分发该触摸事件,具体可以看里面的源码,主要看是否该View是否被屏蔽了。
intercepted变量标志Group是否拦截Touch事件的传递,会调用ViewGroup的onInterceptTouchEvent方法进行判定。