Android事件分发机制之ACTION_DOWN

简介: 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。

总结


这样我们得到几个结论:

1、ViewGroup分发事件down的时候,会遍历自己的子view,从前面的到后面的


for (int i = childrenCount - 1; i >= 0; i--) {
复制代码


然后判断子view的区域是否包含事件,如果包含则进行处理。

所以同级分发时,即两个同级的view叠加在一起时,先分发给前面的view。

2、如果所有的child都不消费ACTION_DOWN事件,那么实际上child并不是收不到任何事件,而是ACTION_DOWN会分发给所有有效范围内的child,但是其他事件就不再分发了。

3、如果有一个child消费了ACTION_DOWN事件,那么后续的事件会直接分发给这个child,不再经过其他child。但是注意,在分发ACTION_DOWN事件时,排在这child前面的child还是会分发到ACTION_DOWN事件,但是也仅仅是ACTION_DOWN事件。

所以整个Touch事件分发过程中,ACTION_DOWN是至关重要的,我们通常考虑的返回值或继续分发的问题,实际上都是讨论ACTION_DOWN这个事件的,基本上ACTION_DOWN事件分发确定了,后续事件的分发就基本确定下来了。但是注意在后续的事件中,依然需要判断InterceptTouchEvent。


目录
相关文章
|
存储 安全 Android开发
探索Android与iOS的隐私保护机制
在数字化时代,移动设备已成为我们生活的一部分,而隐私安全是用户最为关注的问题之一。本文将深入探讨Android和iOS两大主流操作系统在隐私保护方面的策略和实现方式,分析它们各自的优势和不足,以及如何更好地保护用户的隐私。
|
12月前
|
消息中间件 Android开发
Android Handler的使用方式以及其机制的简单介绍
Handler 是 Android 中实现线程间通信的重要机制,可传递任意两线程数据。常用场景包括子线程向主线程(UI 线程)传递结果,以及主线程向子线程发送消息。其核心涉及四个类:Handler(发送/接收消息)、Message(消息载体)、MessageQueue(消息队列)和 Looper(消息循环泵)。基本流程为:Handler 发送 Message 至 MessageQueue,Looper 从队列中按 FIFO 取出并处理。
368 0
|
Linux Android开发 iOS开发
深入探索Android与iOS的多任务处理机制
在移动操作系统领域,Android和iOS各有千秋,尤其在多任务处理上展现出不同的设计理念和技术实现。本文将深入剖析两大平台在后台管理、资源分配及用户体验方面的策略差异,揭示它们如何平衡性能与电池寿命,为用户带来流畅而高效的操作体验。通过对比分析,我们不仅能够更好地理解各自系统的工作机制,还能为开发者优化应用提供参考。
|
算法 Linux 调度
深入探索安卓系统的多任务处理机制
【10月更文挑战第21天】 本文旨在为读者提供一个关于Android系统多任务处理机制的全面解析。我们将从Android操作系统的核心架构出发,探讨其如何管理多个应用程序的同时运行,包括进程调度、内存管理和电量优化等方面。通过深入分析,本文揭示了Android在处理多任务时所面临的挑战以及它如何通过创新的解决方案来提高用户体验和设备性能。
964 1
|
Android开发
Android面试高频知识点(1) 图解Android事件分发机制
Android面试高频知识点(1) 图解Android事件分发机制
|
消息中间件 存储 Java
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
|
消息中间件 存储 Java
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
417 1
|
Android开发
Android面试高频知识点(1) 图解 Android 事件分发机制
Android面试高频知识点(1) 图解 Android 事件分发机制
240 1
|
消息中间件 存储 Java
Android消息处理机制(Handler+Looper+Message+MessageQueue)
Android消息处理机制(Handler+Looper+Message+MessageQueue)
561 2
|
存储 安全 数据安全/隐私保护
探索安卓与iOS的隐私保护机制####
【10月更文挑战第15天】 本文深入剖析了安卓和iOS两大操作系统在隐私保护方面的策略与技术实现,旨在揭示两者如何通过不同的技术手段来保障用户数据的安全与隐私。文章将逐一探讨各自的隐私控制功能、加密措施以及用户权限管理,为读者提供一个全面而深入的理解。 ####
1065 1

热门文章

最新文章