Android事件分发机制之ACTION_DOWN

Posted BennuCTech

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android事件分发机制之ACTION_DOWN相关的知识,希望对你有一定的参考价值。

前言

android的事件分发机制也是老生常谈了,这篇文章并不是笼统的介绍这个机制,而是针对ACTION_DOWN这个事件探讨相关的细节。

dispatchTouchEvent

说到Android事件分发,一定绕不开dispatchTouchEvent函数,View和ViewGroup的该函数有很大的不同。

我们来看看ViewGroup的dispatchTouchEvent函数,它的部分源码如下:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) 
    ...
    if (onFilterTouchEventForSecurity(ev)) 
        ...
        boolean alreadyDispatchedToNewTouchTarget = false;
        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) 
                final int actionIndex = ev.getActionIndex(); // always 0 for down
                final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                        : TouchTarget.ALL_POINTER_IDS;

                removePointersFromTouchTargets(idBitsToAssign);

                final int childrenCount = mChildrenCount;
                if (newTouchTarget == null && childrenCount != 0) 

                    ...

                    for (int i = childrenCount - 1; i >= 0; i--) 
                        ...
                        if (!child.canReceivePointerEvents()
                                || !isTransformedTouchPointInView(x, y, child, null)) 
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        
                        ...

                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) 

                            ...

                            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);
                    
                    if (preorderedList != null) preorderedList.clear();
                

                ...
            
        

        // Dispatch to touch targets.
        if (mFirstTouchTarget == null) 
            // No touch targets so treat this as an ordinary view.
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
         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;
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) 
                    handled = true;
                 else 
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) 
                        handled = true;
                    
                    ...
                
                predecessor = target;
                target = next;
            
        

        ...
    

    if (!handled && mInputEventConsistencyVerifier != null) 
        mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
    
    return handled;

可以看到整个分发有几个关键因素:interceptedcanceledmFirstTouchTargetalreadyDispatchedToNewTouchTarget

intercepted、canceled比较好理解,重点来说说后面两个因素的是如何影响整个分发的。

ACTION_DOWN

一个完整的事件应该包含ACTION_DOWN、ACTION_MOVE、ACTION_UP。其中ACTION_DOWN是开始也是关键。

从上面dispatchTouchEvent源码中可以看到首先单独对ACTION_DOWN事件进行了处理,对所有child进行遍历,是从后向前遍历的,所以在处理上面的也就是最后添加的view会先得到事件

for (int i = childrenCount - 1; i >= 0; i--) 

对于每个child,会先判断事件是不是发生在它的区域内,不是则不处理:

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

如果在区域内,则继续执行,下面dispatchTransformedTouchEvent这个函数就是下发事件的,我们来看下部分源码:

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) 

    ...

    if (newPointerIdBits == oldPointerIdBits) 
        if (child == null || child.hasIdentityMatrix()) 
            if (child == null) 
                handled = super.dispatchTouchEvent(event);
             else 
                final float offsetX = mScrollX - child.mLeft;
                final float offsetY = mScrollY - child.mTop;
                event.offsetLocation(offsetX, offsetY);

                handled = child.dispatchTouchEvent(event);

                event.offsetLocation(-offsetX, -offsetY);
            
            return handled;
        
        transformedEvent = MotionEvent.obtain(event);
     else 
        transformedEvent = event.split(newPointerIdBits);
    

    // Perform any necessary transformations and dispatch.
    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);
    

    // Done.
    transformedEvent.recycle();
    return handled;

有不少逻辑在里面,但是仔细观察可以发现,不论那个条件,执行的代码都比较类似,如下:

if (child == null) 
    handled = super.dispatchTouchEvent(event);
 else 
     ...
    handled = child.dispatchTouchEvent(event);
     ...

当child不为null的时候,执行child的dispatchTouchEvent;为null时执行父类的dispatchTouchEvent,即View的dispatchTouchEvent函数,这个函数里会执行onTouchEvent等。所以在ViewGroup是没有onTouchEvent等函数的代码。

由于这时child不为null,所以执行了child的dispatchTouchEvent函数.

回到之前的ACTION_DOWN流程中,根据dispatchTransformedTouchEvent返回值进行不同的处理:

返回ture

如果返回true,即有一个child消费了ACTION_DOWN事件,可以看到后续执行了addTouchTarget函数,同时将alreadyDispatchedToNewTouchTarget置为true。

if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) 
    ...
    newTouchTarget = addTouchTarget(child, idBitsToAssign);
    alreadyDispatchedToNewTouchTarget = true;
    break;

addTouchTarget函数源码如下:

private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) 
    final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
    target.next = mFirstTouchTarget;  //初始mFirstTouchTarget为null,所以这里next是null
    mFirstTouchTarget = target;
    return target;

关键的一点是对mFirstTouchTarget进行了赋值。所以说true的处理是为mFirstTouchTarget赋值,将alreadyDispatchedToNewTouchTarget置为true
最后的break则跳出循环,不再遍历其他child。

返回false

如果返回false,即没有任何一个child消费ACTION_DOWN事件,直接跳过if代码,这样mFirstTouchTarget为null。

mFirstTouchTarget

那么mFirstTouchTarget、alreadyDispatchedToNewTouchTarget这两个属性在分发过程中的作用是什么?我们分别来说:

1、mFirstTouchTarget为null

mFirstTouchTarget为null,进入if语句执行dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS)

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

由于child是null,在dispatchTransformedTouchEvent代码中可以看到不再给任何child分发,而是调用了super.dispatchTouchEvent,即ViewGroup自己处理

这样ACTION_DOWN事件分发完了。其他事件分发时由于不再走ACTION_DOWN的处理过程,所以mFirstTouchTarget会一直为null,所以其他事件也不再向下分发了,直接ViewGroup自己处理

2、mFirstTouchTarget不为null

mFirstTouchTarget不为null,进入else语句中,会执行一个while循环

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;
        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) 
            handled = true;
         else 
            final boolean cancelChild = resetCancelNextUpFlag(target.child)
                    || intercepted;
            if (dispatchTransformedTouchEvent(ev, cancelChild,
                    target.child, target.pointerIdBits)) 
                handled = true;
            
            ...
        
        predecessor = target;
        target = next;
    

这时由于alreadyDispatchedToNewTouchTarget为true,所以直接给handled赋值true并不做任何处理。因为之前代码中child对ACTION_DOWN事件已经响应,所以这里的alreadyDispatchedToNewTouchTarget是为了防止重复分发ACTION_DOWN事件。

这样ACTION_DOWN事件分发完成后,分发其他事件时,alreadyDispatchedToNewTouchTarget被重新赋值false,由于不再走ACTION_DOWN的处理过程,所以alreadyDispatchedToNewTouchTarget就一直是false了,而mFirstTouchTarget会一直保持不变。在这个while循环中则会执行else语句,通过执行dispatchTransformedTouchEvent将事件直接分发给mFirstTouchTarget对应的child,即之前消费ACTION_DOWN事件的child。

总结

这样我们得到几个结论:

以上是关于Android事件分发机制之ACTION_DOWN的主要内容,如果未能解决你的问题,请参考以下文章

Android事件分发机制初识

Android6.0 ViewGroup/View 事件分发机制详解

Android 事件传递机制进阶

View的事件分发机制解析

Android事件分发机制完全解析,带你从源码的角度彻底理解

浅谈Android 事件分发机制