UI事件分发机制(上层)
2022-04-03 本文已影响0人
天上飘的是浮云
之前跟踪了下,手指点击屏幕。点击事件从底层传到App的Activty的过程。今天,来跟踪和理解下Android上层的事件分发机制和策略。
一、事件的L型链和U型链
L型链:消费了事件
L型链U型链:没有消费事件
U型链二、设计事件分发考虑的点:
- 1、Activity只需要分发事件,它并不需要处理事件:dispatchTouchEvent是主责,onTouchEvent是兜底副业。
- 2、View主要是处理事件,因为它是所有View的标杆,ViewGroup也继承至它,所以也有dispatchTouchEvent方法,主要是分发给自己的onTouchEvent处理。
- 2、ViewGroupe继承至View,主要负责往下分发事件,子View不处理时,也可交由它自己处理。还有一个View没有的功能就是它可以拦截事件。
三、ViewGroup的dispatchTouchEvent方法
在ViewGroup中主要是onInterceptTouchEvent和dispatchTouchEvent起作用。
onInterceptTouchEvent主要是拦截事件
dispatchTouchEvent主要分发事件
public boolean dispatchTouchEvent(MotionEvent ev) {
...
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
...
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
2609行 intercepted = onInterceptTouchEvent(ev);
...
2634行 if (!canceled && !intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
...
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
...
final View[] children = mChildren;
//1. 从上到下,倒序遍历,也就是说最上层优先的到点击事件
for (int i = childrenCount - 1; i >= 0; i--) {
...
//2. 判断点击事件的Point是否在子View的区域范围内
// (其实就是看point是否在left/right/top/bottom范围内);
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
...
//找到子view能接收到Down事件后,调用dispatchTransformedTouchEvent方法
// 看子View是否处理事件,如果处理,则将子View添加到TouchTarget里。
// 并赋给mFirstTouchTarget。以便下次Move或Up事件时,
// 不需要再次遍历寻找子View。
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
...
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
...
}
...
}
}
// Dispatch to touch targets.
2739行 if (mFirstTouchTarget == null) {
// 被拦截或者没有子View处理通过dispatchTransformedTouchEvent
// 交给自己onTouchEvent处理
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
...
TouchTarget target = mFirstTouchTarget;
while (target != null) {
//move、up事件直接分发,不在寻找
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
}
}
}
...
return handled;
}
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
...
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);
}
...
}
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
dispatchTouchEvent中有几个关键点:
-
1、onInterceptTouchEvent返回true即被拦截,直接到2739行,而mFirstTouchTarget也==null,所以会直接调用dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS)交由自己的onTouchEvent处理。 -
2、如果没有拦截会进入到2634行的if代码块中,这里将会做四件事:1、必须是Down事件才会进;2、需要将它的ziView按Z轴由上到下,倒序遍历,寻找能接收事件的子View;3、判断点击事件的Point是否在子View的区域范围内(其实就是看point是否在left/right/top/bottom范围内);4、找到子view能接收到Down事件后,调用dispatchTransformedTouchEvent方法看子View是否处理事件,如果处理,则将子View添加到TouchTarget里。并赋给mFirstTouchTarget。以便下次Move或Up事件时,不需要再次遍历寻找子View。
-
3、用handled变量来接收View是否处理事件。
四、View的dispatchTouchEvent和onTouchEvent方法
View中的dispatchTouchEvent主要是分发给自己来处理事件。
- 1、13424行代码,是View设置onToucheLisener时,如果onTouch方法返回FALSE,表示不拦截onTouchEvent方法和onClickListener;如果onTouch方法返回ture,表示拦截调onTouchEvent方法,onClickListener是在onTouchEvent中调用,所以也不会调用了 。
13424行 if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
13430行 if (!result && onTouchEvent(event)) {
result = true;
}
- 2、onTouchEvent时,up事件中performClickInternal -> performClick -> li.mOnClickListener.onClick(this); 这是调用onClickListener的调用链