事件传递方向
activity -> viewGroup -> view
事件序列.png
事件的传递入口
事件触发 -> 硬件 -> Native -> 通过JNI ->
Activity.dispatchTouchEvent() ->
PhoneWindow.superDispatchTouchEvent() -> DecorView.superDispatchTouchEvent() ->
ViewGroup.dispatchTouchEvent()
事件的开始
事件的触发从手指触摸屏幕开始,屏幕硬件接受到触摸事件后,交由底层系统处理(native层)。native层再通过JNI通知Activity,触发dispatchTouchEvent方法,开始事件在view中的一系列传递。
事件的传递
Activity对事件的传递最终是交给DecorView(顶层容器)处理,DecorView继承自FrameLayout,也是一个ViewGroup。
事件的传递始终是在ViewGroup 和 View中逐层传递。
ViewGroup控制事件传递的3个方法
* dispatchTouchEvent;事件分发,向下传递事件
* onInterceptTouchEvent;事件拦截
* onTouchEvent;事件消费/处理
View控制事件传递的2个方法
* dispatchTouchEvent;事件分发,最终调用事件处理
* onTouchEvent;事件消费/处理
需要注意两个dispatchTouchEvent方法:ViewGroup继承自View,重写了View的dispatchTouchEvent方法,主要功能是对 子View 或 子ViewGroup进行事件的传递;而View的dispatchTouchEvent方法,主要功能是调用事件处理的前期工作。
本文源码版本,api:28
Activity中的传递路径
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();//空方法,重写用于事件触发时被调用
}
//最终调用DecorView的dispatchTouchEvent()
//即ViewGroup的dispatchTouchEvent()
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
Activity的dispatchTouchEvent()分两种情况:
事件被消费,return true
没有被消费,调用activity的onTouchEvent方法
ViewGroup中的传递路径
//ViewGroup的dispatchTouchEvent()中
final boolean intercepted;//拦截标记
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//获取拦截方法中的值
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
在事件分发的过程中,先获取是否需要拦截,调用onInterceptTouchEvent()。
拓展点
disallowIntercept :不允许拦截的标记。
子view可以调用父容器的/requestDisallowInterceptTouchEvent/方法对其进行修改。
//ViewGroup的dispatchTouchEvent()中
for (int i = childrenCount - 1; i >= 0; i—) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
。。。
//寻找可传递的子view代码
。。。
//重点1
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//重点2
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
。。。
}
//——————————dispatchTransformedTouchEvent()——————————
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
//——————————dispatchTransformedTouchEvent()——————————
遍历查找可传递事件的子View。
重点1
在寻找到子View后,调用dispatchTransformedTouchEvent()。
子View如果为空,就调用自己的父类方法
子View不为空,就调用子View的事件分发,向下一层View传递事件。
子View继续重复dispatchTouchEvent()方法,类似于递归方法的调用。
等待返回handled值。
举个例子:当前有ViewGroupA,ViewGroupB,ViewA。ViewGroup包裹ViewGroupB,ViewGroupB包裹ViewA。
2.png重点2
addTouchTarget方法
主要功能:记录被消费的事件传递过程中每个view,形成完整的事件传递路径。
下次的MOVE,UP事件等都只需要查找传递路径即可分发事件,不必再重复查找View。
View的事件处理
//View的dispatchTouchEvent()
。。。
ListenerInfo li = mListenerInfo;//包含各种点击,双击,长按等监听
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
。。。
首先判断ListenerInfo是否为空,ListenerInfo中包含各种点击,双击,长按等监听。所以这类监听的优先级是高于onTouchEvent。
在没有设置监听的情况下,调用onTouchEvent方法,如果onTouchEvent方法进行了消费,返回true。
View的/dispatchTouchEvent/返回的true,被父容器ViewGroup的/dispatchTransformedTouchEvent/接收到,一路继续向上传递,最终完成了事件路径的记录。
事件传递的完整路径
ToucheEvent.png总结
事件的传递回顾
- Activity接受事件,交给ViewGroup处理。
- ViewGroup遍历子View,递归查找被消费的子View。
- 记录递归handled=true的View,形成完整的事件传递链。
- 其余一系列事件直接经过 传递链 传递 事件。
ViewGroup的3个方法和View的2个方法之间的传递过程比较简单。
关键点是ViewGroup的向下传递,递归查找消费子View。
网友评论