likes
comments
collection
share

面试官:ViewGroup和View可以同时接收事件吗?

作者站长头像
站长
· 阅读数 4

面试官: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 不可以同时接收事件。