可以结合另一篇源码分析:Android事件分发机制的源码分析。 -
1、为什么有事件分发机制?
屏幕上的View可能会重叠在一起,当有多个View可以响应点击事件,要用事件分发机制解决这问题。
2、事件分发相关知识
2.1事件分发究竟是什么?
2.1.1 MotionEvent
当用户点击屏幕里View或者ViewGroup的时候,将会产生一个事件对象,这个事件对象就是MotionEvent对象(事件的类型,触摸的位置,以及触摸的时间)。
MotionEvent的值有:
MotionEvent.ACTION_DOWN:用户触摸View&ViewGroup。
MotionEvent.ACTION_MOVE:用户手指移动View&ViewGroup。
MotionEvent.ACTION_UP:用户手指离开屏幕。
MotionEvent.ACTION_CANCEL:事件退出了,不是用户导致的。
MotionEvent.ACTION_CANCEL是什么?
当控件收到前驱事件之后,如果后面事件被父控件拦截,那么当前当前控件会收到 CANCEL事件,并且把这个事件会传递给它的子事件。
产生的条件:
父View收到ACTION_DOWN,如果没有拦截事件,则ACTION_DOWN前驱事件被子View接收,父View后续事件会发送到子View。
如果在父View中拦截ACTION_UP或ACTION_MOVE,在第一次父View拦截消息的瞬间,父View指定子View不接受后续消息了,
同时子View会收到ACTION_CANCEL事件。
2.1.2 事件分发的本质(定义)
本质将点击屏幕产生的MotionEvent对象传递到某个具体的View然后处理消耗这个事件的整个过程。
2.1.3 事件怎么产生&事件分发产生的事件在哪些对象之间传递
用户触摸,滑动,离开屏幕时,这个时候就产生了事件,Android系统将事件封装为MotionEvent对象(事件的类型,事件触发的时间,以及触摸在屏幕的哪个位置等)。
MotionEvent对象在哪些对象之间传递呢?Activity&ViewGroup&View。
2.1.4 三个重要的有关事件分发的方法
1)dispatchTouchEvent
-> 若此 View接受到事件,此方法一定被调用。返回结果受此 View的onTouchEvent和下级的dispatchTouchEvent方法影响,表示是否消耗此事件。
2)onInterceptTouchEvent(只有 ViewGroup有。)
-> 在上述方法dispatchTouchEvent内部调用,用来判断是否拦截某个事件。
当前View拦截了某个事件,此方法不会被再次调用,返回结果表示是否拦截当前事件。
3)onTouchEvent
-> 在dispatchTouchEvent内部调用,用来处理点击事件,返回结果表示是否消耗当前事件,
如果不消耗,当前View无法再次接收到事件。
大致逻辑流程(伪代码):
public boolean dispatchTouchEvent(MotionEvent ev){
boolean consume = false;//记录返回值
if(onInterceptTouchEvent(ev)){//判断是否拦截此事件
consume = onTouchEvent(ev);//如果当前确认拦截此事件,那么就处理这个事件
}else{
consume = child.dispatchToucnEvent(ev);//如果当前确认不拦截此事件,那么就将事件分发给下一级
}
return consume;
}
小结:
从 ViewGroupA开始,点击事件产生,A.dispatchTouch()被调用。
-> 情况1:要拦截,A.onInterceptTouchEvent()返回true。 A.onTouchEvent()然后被调用,自己处理。
-> 情况2:不拦截,A.onInterceptTouchEvent()返回false。调用子View.dispatchTouchEvent()再进行一样的判断。
View处理事件伪代码:
View.OnTouchListener() {
boolean result; //记录返回值
if(onTouch()){
View.onTouchEvent()不调用。
}else{
View.onTouchEvent()调用。
}
}
在onTouchEvent()中,若设置onClickListener则,onClick()也会被调用。
传递过程:Activity–>Window–>View,View在按照事件分发机制分发事件。如果最底端的 View.onTouchEvent()返回false,则
回调上一层的 onTouchEvent(),最终会返回给 Activity。
注意:
1)正常情况,一个事件只被一个 View拦截消耗。如果硬要两个 View去处理,在ViewA的.onTouchEvent()强行传递给ViewB处理。
2)某 View决定拦截,onInterceptTouchEvent()不会被调用。
3)某 View开始处理事件,如果不消耗 ACTION_DOWN事件(onTouchEvent返回了false),那么同一序列中的事件都不会再给它处理了。
4)View不消耗ACTION_DOWN以外的事件,那么这个点击事件会消失,此时父元素的onTouchEvent并不会调用,并且当前View可以持续收到后续的事件,
最终这些消失的点击事件会传递给Activity处理。
5)ViewGroup默认不拦截任何事件。Android源码中ViewGroup的onInterceptTouchEvent方法默认返回false。
6)View没有onInterceptTouchEvent方法,一旦点击事件传递给它,那么它的onTouchEvent方法就会被调用。
7)View的onTouchEvent默认消耗事件(返回true),除非它是不可点击的(clickable和longClickable同时为false)。
View的longClickable属性默认为false,clickable属性要分情况,比如Button的clickable属性默认为true,而TextView的clickable属性默认为false。
8)onClick会发生的前提是当前View是可点击的,并且它接收到了down和up事件。
9)事件传递由父->子,子通过 requestDisallowInterTouchEvent方法可以在子元素中干预父元素的事件分发过程,但是ACTION_DOWN事件除外。
参考网址:
https://blog.csdn.net/ClAndEllen/article/details/79365369
https://blog.csdn.net/vansbelove/article/details/78416791
https://www.jianshu.com/p/e99b5e8bd67b
https://www.jianshu.com/p/8bc0765dffc9
https://www.cnblogs.com/Claire6649/p/5947139.html
https://blog.csdn.net/mydreamongo/article/details/30465613
上的