面试官:ViewGroup和View可以同时接收事件吗?
面试官:ViewGroup和View可以同时接收事件吗? 我:???
这里不卖关子了,答案是不会。
单指事件分发
一般我们熟悉的事件分发机制是这样的:
当ACTION_DOWN事件下发时,ViewGroup 通过 dispatchTouchEvent 方法分发事件,内部通过 onInterceptTouchEvent 方法判断是否拦截。如果拦截,就调用ViewGroup自己的 onTouchEvent 方法。如果不拦截就调用子 View 的dispatchTouchEvent 方法,该方法内部默认情况会调用子 View 的 onTouchEvent 方法。事件分发网上文章很多,这里就不多介绍了。这里需要了解的一点是,当一个 View 决定处理 ACTION_DOWN 事件时,后面完整的事件都会交给这个 View 处理。(不考虑CANCEL的情况)
完整的事件是指:从 ACTION_DOWN --> ACTION_MOVE --> ACTION_MOVE --> ACTION_UP/ACTION_CANCEL 这一串事件
该功能其实是靠 TouchTarget 这里类来实现的,核心代码以及注释如下:
/*
* 描述已触摸的视图及其捕获的指针的ID。
* 此代码假设指针ID始终在0..31范围内,因此
* 它可以使用位字段来跟踪存在哪些指针id。
*/
private static final class TouchTarget {
private static final int MAX_RECYCLED = 32;
private static final Object sRecycleLock = new Object[0];
private static TouchTarget sRecycleBin;
private static int sRecycledCount;
public static final int ALL_POINTER_IDS = -1; // all ones
// 被触碰的子 View
@UnsupportedAppUsage
public View child;
//触碰的所有手指,使用位字段来跟踪存在哪些指针id
public int pointerIdBits:;
// 通过链表的形式,记录上一个 TouchTarget
public TouchTarget next;
public static TouchTarget obtain(@NonNull View child, int pointerIdBits) {
...
}
public void recycle() {
...
}
}
TouchTarget 的作⽤是记录每个⼦ View 是被哪些 pointer(⼿指)按下的,它的结构是单向链表。重要的三个属性如下:
- child:被触碰的子 View
- pointerIdBits:触碰的所有手指,使用位字段来跟踪存在哪些指针id
- next:通过链表的形式,记录上一个 TouchTarget
当 ACTION_DOWN 时,处理事件的 View 会被保存在 TouchTarget 中。下发 ACTION_MOVE 事件时则会刚才使用保存在 TouchTarget 中的 View 来处理,所以一个完整的事件会全部交给该 view。
多指事件分发
多指情况下,即事件为 ACTION_POINTER_DOWN 时。ViewGroup 会遍历子 view,判断是否多个手指在同一个 view 上;如果不在,这时会创建新的 TouchTarget,并加到单链表首部,同时调用该子 view 的 dispatchTouchEvent 方法。核心代码如下:
//ViewGroup
public boolean dispatchTouchEvent(MotionEvent ev) {
...
TouchTarget newTouchTarget = null;
if (!canceled && !intercepted) {
...
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
if (newTouchTarget == null && childrenCount != 0) {
//寻找接受事件的子View
...
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
// 如果没有View接受事件,就让最近添加的目标View处理
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
}
如果子View接受事件,这时 newTouchTarget 就不为 null,之后的流程就和单指一样了。如果子View不接受事件,那么就会让最近添加的目标 View 处理该事件。我们可以看到,多指情况下,即使子 View 不接受事件,也不会把事件交给 ViewGroup,而是交给最近添加的目标View处理,因此我们可以知道 ViewGroup 和 View 不可以同时接收事件。
转载自:https://juejin.cn/post/7351682240747520037