事件分发是android里的解决事件冲突的一种机制。一般我们的布局都是一层叠着一层,那么当我们手指点击或者滑动的时候,屏幕怎么知道哪一个控件该响应,哪一个控件该滑动呢?这依靠的就是事件分发机制。
本次文章源码细节不扣,但是主要流程会列出来,感兴趣的可以去源码中找对应的逻辑,有疑问可以留言哦。
目录
1.流程解读:
从Activity--->DecorView:
当我们在activity中触摸屏幕点击的时候,它的流程是这样的:
基本流程:
- 从activity的dispatchTouchEvent开始,调用到DecorView的dispatchTouchEvent()(DevorView是ViewGroup的子类,使用ViewGroup的事件分发机制)
- DecorView是所有view的父布局,它的dispatchTouchEvent() 会递归从外到内进行事件分发。
从DecorView到子view:
DecorView和它的ziview是父布局和子布局的关系,所以他们的流程就是父布局到子布局事件分发的过程,之后所有的过程都是这样递归的。记住一点,事件分发的流程顺序是从父布局到子布局。
事件分发涉及到的几个关键方法:
- ①dispatchTouchEvent() // 事件分发的总调度方法
- ②requestDisallowInterceptTouchEvent() // 子view限制父类不能拦截事件
- ③onInterceptTouchEvent() // 决定当前的view是否拦截事件
- ④onTouchEvent() // 当前view事件操作。返回值代表是否消费了事件
其中③④是在①中执行的,②是在外部执行的。
ok,下面从源码的角度分析,在这之前,先贴几条结论:
- 一般讲事件的开始和结束是指事件流的开始和结束。一个事件流指一系列操作,一般是以DOWN事件开始,UP或cancel事件结束。例如 DOWN-->MOVE-->MOVE-->....-->UP 这是一个事件流。
- 一个事件流中,如果一个View在DOWN事件的时候消费了事件,那么在父view不拦截的情况下,后续事件则都由它处理。
- 一个事件流中,父view可以在任何阶段拦截事件。当父view拦截了事件,那么后续的事件就由父view来接管了。
- 子view可以通过设置parent.requestDisallowInterceptTouchEvent(true)来禁止父view拦截事件。
下面进入源码了,主要代码都在ViewGroup#dispatchTouchEvent()中,代码就不贴了。可以去看一下这个方法的源码,大致流程如下:
- ① 处理down事件:如果是DOWN事件,那么清除触摸对象mFirstTouchTarget,重置状态。
- ② 检查事件拦截:通过状态和onInterceptTouchEvent() 的返回值来设置intercepted
- ③ 如果不拦截&不取消(其他过滤条件省略)&当前事件是DOWN事件,按照从前往后的顺序遍历子view。遍历过程:
- 如果找到子view消费了事件,那么触摸对象mFirstTouchTarget赋值,alreadyDispatchedToNewTouchTarget=true,跳出循环。
- ④ 如果触摸对象mFirstTouchTarget为空,那么自己消费事件,即执行自己的onTouchEvent()
- ⑤ 如果触摸对象不为空,则判断是否已经处理过(依据alreadyDispatchedToNewTouchTarget和target)
- --如果未处理过:判断是否拦截(即intercepted)
- --如果不拦截,继续交给target处理事件
- --如果拦截,则向target传递一个cancel事件,然后清除mFristTouchTarget。 然后下一个后续事件中自己就消费事件了。
上面的流程在源码中都能找到,说的比较详细,下面我们来从实际场景分析以上流程。
2.场景分析:
举例三个常见的场景
场景一:
父view不拦截,子view消费事件。
事件流开始===
1.DOWN事件:
经过①②后:
mFirstTouchTarget=null
intercepted=false
经过③后:
mFirstTouchTarget=taget
alreadyDispatchedToNewTouchTarget=true
这时已经处理了事件
④过
⑤过
2.MOVE事件
经过①②,
mFirstTouchTarget=target
intercepted=false
③过
④过
⑤满足触摸对象不为空,未处理过,不拦截,继续交给target处理事件
之后的MOVE/UP事件流程同2
===事件流结束
场景二:
父view拦截事件,子view消费事件
事件流开始===
1.DOWN事件:
经过①②后:
mFirstTouchTarget=null
intercepted=true
③过
④满足mFirstTouchTarget=null, 自己消费事件
⑤过
之后的事件都同1
===事件流结束
场景三:
父view开始不拦截事件,当MOVE时开始拦截,子view消费事件。
事件流开始===
1.DOWN事件:
经过①②后:
mFirstTouchTarget=null
intercepted=false
经过③后:
mFirstTouchTarget=taget
alreadyDispatchedToNewTouchTarget=true
这时已经处理了事件
④过
⑤过
2.MOVE事件
经过①②后:
mFirstTouchTarget=target
intercepted=true
③过
④过
⑤满足 触摸对象不为空,未处理过,拦截,则向target传递一个cancel事件,然后清除mFristTouchTarget。
3.下一个MOVE事件
经过①②后:
mFirstTouchTarget=null
intercepted=true
③过
④满足mFirstTouchTarget=null, 自己消费事件
⑤过
之后的事件都同3
===事件流结束