Android事件分发机制

106 阅读3分钟

ViewGroup事件分发

viewgroup通过dispatchTouchEvent进行事件分发,在一组触摸事件中,触摸点id不会发生改变。

认定ACTION_DOWN(按下事件)作为一组事件的开始,每当收到按下事件时,都会清空touchTarget,然后进行子view进行事件分发。在对子view进行事件分发时,会把消耗本次事件的view保存到touchTarget中。

由于按钮事件发生时,touchTarget会被清空,所以按钮事件之外的事件(滑动,抬起)才能在touchTarget事件中找到之前响应的view,在进行事件分发时遍历view时,会去touchTarget中寻找这次分发的view是否存在。

遍历子view之前的工作

if (!canceled && !intercepted) {

    View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
            ? findChildWithAccessibilityFocus() : null;

    if (actionMasked == MotionEvent.ACTION_DOWN
            || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
            || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            .......
            遍历子view
}

我们可以看到,拦截事件只作用于DOWN事件,其次遍历子view也只发生在以下事件中MotionEvent.ACTION_DOWN、MotionEvent.ACTION_POINTER_DOWN、MotionEvent.ACTION_HOVER_MOVE,我们可以看到没有MOVE、UP事件。也就是子viewDOWN事件不要的话,这一组事件的其他事件也收不到了。

事件分发遍历子view

首先判断当前点击的位置是否在当前的子view中,不在的话执行continue,在验证下一个view,遍历默认是按addView的倒置顺序遍历。

if (!canViewReceivePointerEvents(child)
        || !isTransformedTouchPointInView(x, y, child, null)) {
    ev.setTargetAccessibilityFocus(false);
    continue;
}

如果点击的位置在当前viewGroup的子view的范围(即点击的是否为当前view),那么执行这个子view的dispatchTouchEvent前先判断touchTarget中是否已经存在当前view,存在的话说明当前的事件是此view前一个事件的后续事件,需要换个一个跳出当前view的遍历,执行其他的逻辑。这里也有一个作用:把这段放在当前点击的位置是否在当前子view的范围之后,在后续的事件过来后能保证这个事件是该view的后续事件。

newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
    // Child is already receiving touch within its bounds.
    // Give it the new pointer in addition to the ones it is handling.
    newTouchTarget.pointerIdBits |= idBitsToAssign;
    break;
}

如果touchTarget中不存在的话,说明这是这一组事件的第一个事件,执行这个view的dispatchTouchEvent,如果这个view消耗掉了这个事件(也即onTouch或onTouchEvent返回true)并把这个view加入到touchTarget中。

resetCancelNextUpFlag(child);
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();
    newTouchTarget = addTouchTarget(child, idBitsToAssign);
    alreadyDispatchedToNewTouchTarget = true;
    break;
}

如果没有消耗掉事件

// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);

TouchTarget事件分发

当前viewGroup没有找到可以消耗本事件的View时,就会再次把本事件分发给自己(但自己也不一定会消耗掉,也就是说还会往父View传递消耗结果),一般是由于点击了子view之外的区域。

if (mFirstTouchTarget == null) {
    // No touch targets so treat this as an ordinary view.
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);
}

如果第一个touchTarget不为null,不管前面是否消耗掉本事件,只要第一个touchTarget不为null都会执行。while(target != null)循环主要用于多点触控点击了不同view调用的,因为多点触控时: actionMasked == MotionEvent.ACTION_POINTER_DOWN,所以不会清空touchTarget。

else {
    // Dispatch to touch targets, excluding the new touch target if we already
    // dispatched to it.  Cancel touch targets if necessary.
    TouchTarget predecessor = null;
    TouchTarget target = mFirstTouchTarget;
    while (target != null) {
        final TouchTarget next = target.next;
        //如果这个事件已经被消耗掉了,就不需要再次dispatch了
        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
            handled = true;
        } else {
        //这里的代码执行只能是由于newTouchTarget = getTouchTarget(child)时返回非null
        
            final boolean cancelChild = resetCancelNextUpFlag(target.child)
                    || intercepted;
            if (dispatchTransformedTouchEvent(ev, cancelChild,
                    target.child, target.pointerIdBits)) {
                handled = true;
            }
            if (cancelChild) {
                if (predecessor == null) {
                    mFirstTouchTarget = next;
                } else {
                    predecessor.next = next;
                }
                target.recycle();
                target = next;
                continue;
            }
        }
        predecessor = target;
        target = next;
    }
}