这是我参与8月更文挑战的第24天,活动详情查看:8月更文挑战
介绍
当我们点击、长按、滑动屏幕时,都会产生一个个事件,这些事件被封装为MotionEvent。分发机制就是将一个事件从屏幕传递给应用的各个View/ViewGroup,然后由其中的View来消费或者忽略这一个事件。
View的继承关系
-
继承自View的只能处理事件 像
ImageView它是继承自View的没有子View,当它遇到事件时,只有俩个选择处理这个事件和不处理这个事件。 -
继承自ViewGroup的才能进行分发事件 ViewGroup相当于一个容器包含
子View可以进行分发事件,
分发对象
具体在传递事件的时候,是由以下三个方法来控制的:
- dispatchTouchEvent : 分发事件
- onInterceptTouchEvent : 拦截事件
- onTouchEvent : 消费事件
事件分发的流程
事件分发机制使用的是责任链设计模式,从Activity如果传到最下层的View都没有组件处理该事件,该事件会依次回传到Activity。
事件流程图
graph TD
A[Avtivity#diapthchTouchEvent] --> B[PhoneWindow#superDispatchTouchEvent]
B --> C[DecorView#superDispatchTouchEvent]
C --> D[ViewGroup#dispatchTouchEvent]
D --> |处理事件| F[View#dispatchTouchEvent]
F --> G[View#onTouchEvent]
事件分发源码分析
Acticity.java
这里优先响应子View,因为当父View决定不拦截子View后,就会调
dispatchTouchEvent,ViewGroup的这个方法会先去遍历调用子View的dispatchTouchEvent,如果都返回false,该事件会依次回传到Activity。 先往下看待会回来理解这句话。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();//空实现,锁屏相关
}
//Window仅存在一个实例PhotoWindow,在Window里找不到代码去PhotoWindow里找
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
Avtivity中调用Window.superDispatchTouchEvent(ev),但是在Window类中没有superDispatchTouchEvent()的方法,PhotoWindow作为Window仅存在的一个实例存在该方法。
PhoneWindow.java
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
PhoneWindow中调用了DecorView.superDispatchTouchEvent()
DecorView
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
DecorViewextendsFrameLayoutextendsViewGroup
dispatchTouchEvent真正的逻辑是存在于ViewGroup中的
ViewGroup.java
上诉代码:
ACTION_DOWN事件一定会交由ViewGroup处理,子View没办法拦截,因为resetTouchState()会把requestDisallowInterceptTouchEvent()所置的标志位重置。
intercept表示是否拦截事件。
onInterceptTouchEvent()对intercept进行初始化。大多数情况下,onInterceptTouchEvent()返回值为false,但我们可以重写onInterceptTouchEvent()来改变它的返回值。
canceled检查是否取消,事件被上层是否拦截
可以看到,当
intercept是false以及canceled,会通过for循环去遍历ViewGroup的child,然后调用dispatchTransformedTouchEvent(),如果dispatchTransformedTouchEvent()返回值是true,就去调用addTouchTarget()。
而当intercept是true时,就不会去遍历ViewGroup的child,也更不会调用child的
dispatchTransformedTouchEvent()了。
传入给
dispatchTransformedTouchEvent()的child不为null,所以调用的是子View的dispatchTouchEvent()。
如果子View的dispatchTouchEvent()返回true,则调用addTouchTarget()。
将指定子级的触摸事件添加到列表的开头。
当事件传递给ViewGroup,先去遍历调用child的dispatchTouchEvent(),如果有child的dispatchTouchEvent()返回了true,mFirstTouchTarget就被赋值,否则mFirstTouchTarget就为null。
如果
mFirstTouchTarget为null,就去调用dispatchTransformedTouchEvent()。
未完。。。