View的事件分发机制是解决滑动冲突的基础。
首先要理解两个概念,View
和ViewGroup
,控件如Button
,TexiView
,ImageView
等都继承自View
父类,布局如LinerLayout
,RelativeLayout
等都继承自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
9public 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
,则当前View
的onTouchEvent
方法会被调用;如果返回true,那么onTouchEvent
方法将不会调用。由此可见,给View设置的OnTouchListener
,其优先级比onTouchEvent
要高。在onTouchEvent
方法中,如果当前设置的有onClickListener
,那么它的click方法会被调用,可以看出我们平时所用的onClickListener
优先级最低,位于末端。
当一个点击事件产生后,它的传递过程遵循如下规则:Activity
->Window
->View
,这里的View是顶级View
,顶级View接收到事件后,会按照事件分发机制一步步向下分发事件。考虑一种情况,如果一个View的onTouchEvent返回false,那么它的父容器的onTouchEvent
将会被调用,以此类推。如果所有的元素都不处理这个事件,那么最终这个事件会被传递给Activity
处理,调用Activity
的onTouchEvent
方法。
关于事件分发机制,这里有一些结论:
(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)View
的onTouchEvent
默认都会消耗事件,除非它是不可点击的(clickable
和longClickable
同时为false
,View
的longClickable
默认为false
)。
(9)View
的enable
属性不影响onTouchEvent
的默认返回值。
(10)onClick会发生的前提是当前View是可点击的,并且它收到了down和up事件。
(11)事件传递过程都是由外向内的,通过requestDisallowInterceptTouchEvent
方法可以在子元素中干预父元素的事件分发过程,但是ACTION_DOWN
事件除外。mInputEventConsistencyVerifier
变量是调试用的,不用理会。onFilterTouchEventForSecurity()
表示是否要分发该触摸事件,具体可以看里面的源码,主要看是否该View是否被屏蔽了。intercepted
变量标志Group是否拦截Touch事件的传递,会调用ViewGroup的onInterceptTouchEvent
方法进行判定。