View的事件分发
本文的起点是Activity里的dispatchTouchEvent()
方法,整体的流程如下图
流程梳理
Activity.java
1 | public boolean dispatchTouchEvent(MotionEvent ev) { |
获得Activity的mWindow
,调用其superDispatchTouchEvent()
方法
Window.java
1 | public abstract boolean superDispatchTouchEvent(MotionEvent event); |
是一个抽象方法,手机上获得的Window都是PhoneWindow这个子类
PhoneWindow.java
1 |
|
调用了mDecor
的superDispatchTouchEvent()
方法
mDecor
是一个DecorView对象,它是Window的顶层View,是FrameLayout的子类,DecorView里包括一个标题栏和一个内容View,Activity的setContentView()
方法就是给DecorView设置内容View
DecorView.java
1 |
|
可见可以通过设置回调来拦截触摸事件,如果没有的话就调用父类ViewGroup的dispatchTouchEvent()
ViewGroup.java
1 |
|
`ViewGroup.java
1 | private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { |
View.java
1 | public boolean dispatchTouchEvent(MotionEvent event) { |
可见返回值如果为true,则表示事件已被处理不需再处理
View.java
1 | public boolean onTouchEvent(MotionEvent event) { |
会在ACTION_UP
事件里处理点击事件
同样处理后返回true,否则返回false
View.java
1 | public boolean performClick() { |
这里调用了View.OnClickListener.onClick()
总结流程
事件首先到达Activity,由
dispatchTouchEvent()
分发给顶层布局容器ViewGroup通过
dispatchTouchEvent()
进行分发- 处理DOWN事件,做一些初始化
- 处理拦截
- 如果没有拦截,则遍历子元素,根据DONW事件的信息查找触摸目标,如果有则调用这个触摸目标的
dispatchTouchEvent()
方法,如果是ViewGroup,则重复2,如果是View,则调用其onTouchEvent()
处理事件并返回 - 如果没有找到触摸目标,则调用自己父类的
View.dispatchTouchEvent()
方法,否则在所有的触摸目标上应用触摸事件,排除已处理过的 - 处理UP事件,清理状态
处理返回值,如果为
true
,则之后的同序列事件都由本View处理,如果为false
,则之后的同序列事件交给父布局处理,重复此过程直到顶层布局容器布局都没有处理,则由Activity的
onTouchEvent()
处理
细节整理
拦截原理
actionMasked == MotionEvent.ACTION_DOWN
和mFirstTouchTarget != null
两个条件满足其一的时候会判断是否拦截,前者说明DOWN事件总是判断拦截的,后者说明之前的事件已经交给子元素处理了,所以一旦DOWN事件已经被拦截,mFirstTouchTarget
就是空,那么接下来的MOVE和UP事件就不会再判断拦截了,即所有一系列事件都会被拦截
下面会判断拦截标志位,这个标志位会导致父元素无法拦截除DOWN之外的事件,因为在DOWN到来时会重置标志位,如果没有禁止拦截,那么onInterceptTouchEvent()
确定是否拦截,这个标记可以由子元素调用requestDisallowInterceptTouchEvent()
来设置
返回值
返回值会层层回传,直到Activity那里
Activity.java
1 | if (getWindow().superDispatchTouchEvent(ev)) { |
可见如果是true
,就直接返回了,如果是false
,就走Activity的onTouchEvent()
方法,由Activity处理
对于DOWN事件有额外的处理,DOWN事件里会查找并分发给子元素处理,如果返回的是true
,则会调用addTouchTarget()
,这里面会给mFirstTouchTarget
赋值,返回false
则不会,后面就会做判断,决定后续事件是交给子元素还是自己处理
所以返回true
则由子元素处理,返回false
则由自己处理