Android下的Touch事件分发详解

在 Android 系统中,触摸事件的分发和处理是一个非常重要的部分。了解触摸事件的分发机制对于我们进行界面交互设计和优化具有重要意义。本文将详细介绍 Android 下的 Touch 事件分发机制,包括事件分发的过程、涉及的方法以及 ViewGroup 中事件分发的实现。

一、事件传递路径

触摸事件的传递路径是从 Activity 到 Window,再到 View。具体来说,当一个触摸事件产生时,首先会传递给 Activity 的 dispatchTouchEvent 方法,然后由 Activity 将事件传递给 Window,最后由 Window 将事件传递给顶层的 View。在 View 层级结构中,事件会从上到下(父 View 到子 View)进行传递,直到有一个 View 能够处理这个事件为止。

二、触摸事件的三个关键方法

在 Android 系统中,触摸事件的分发过程涉及到三个重要的方法:dispatchTouchEvent(MotionEvent ev)、onInterceptTouchEvent(MotionEvent ev)和 onTouchEvent(MotionEvent ev)。下面我们分别来看一下这三个方法在 ViewGroup 和 Activity 中的作用。

2.1 dispatchTouchEvent(MotionEvent ev)

此方法用来分发事件。如果当前事件能传递到该 View,该方法一定会被调用。当 Touch 事件发生时,Activity 的 dispatchTouchEvent(MotionEvent ev)方法会以隧道方式将事件传递给最外层 View 的 dispatchTouchEvent(MotionEvent ev)方法,并由该 View 的 dispatchTouchEvent(MotionEvent ev)方法对事件进行分发。

2.2 onInterceptTouchEvent(MotionEvent ev)

此方法用来拦截事件。如果返回值为 true,表示拦截,否则不拦截。在外层 View 的 dispatchTouchEvent(MotionEvent ev)方法返回系统默认的 super.dispatchTouchEvent(ev)情况下,事件会自动的分发给当前 View 的 onInterceptTouchEvent 方法。

2.3 onTouchEvent(MotionEvent event)

此方法用于处理当前事件。如果返回值为 true 表示消耗该事件,否则无法再接收同一个序列的事件。如果 dispatchTouchEvent 方法 return false,事件会以冒泡方式返回给上层的 onTouchEvent 进行消费。

三、事件传递机制:隧道方式和冒泡方式

3.1 冒泡方式和隧道方式的过程

在 Android 中,事件传递机制包括两个主要过程:隧道方式(Tunneling)和冒泡方式(Bubbling)。这两种方式共同构成了 Android 事件传递的完整过程,用于处理 Touch 事件在视图层次结构中的传递和消费。以下是结合dispatchTouchEventonInterceptTouchEventonTouchEvent方法,阐述冒泡方式和隧道方式的过程和特点:

  1. 隧道方式 :隧道方式是一种自上而下的事件传递过程。当 Touch 事件发生时,事件首先传递给最外层的 Activity,然后通过dispatchTouchEvent方法沿着视图层次结构逐级向内传递给子视图。在这个过程中,每个视图(如 ViewGroup)都可以通过onInterceptTouchEvent方法对事件进行拦截。如果某个视图拦截了事件,事件将不再继续传递给更深层的子视图。如果事件没有被拦截,最终会传递到最内层的子视图。

  2. 冒泡方式 :冒泡方式是一种自下而上的事件回传过程。当 Touch 事件未被最内层的子视图消费时(即onTouchEvent方法返回false),事件会沿着视图层次结构逐级向外回传给父视图。在这个过程中,每个视图都可以通过onTouchEvent方法对事件进行处理,如消费事件。如果某个视图消费了事件(即onTouchEvent方法返回true),事件将不再继续回传给更外层的父视图。

整个事件传递过程可以概括为:首先通过隧道方式自上而下地传递事件,然后在未被消费的情况下通过冒泡方式自下而上地回传事件。这种机制允许开发者在不同层次的视图中灵活地处理事件,实现复杂的交互效果。同时,这种机制也有助于提高事件处理的效率,因为在事件被拦截或消费后,事件将不再继续传递或回传,从而减少了不必要的计算开销。

3.2 时序图

sequenceDiagram participant Activity participant ParentView participant ChildView Activity->>ParentView: dispatchTouchEvent ParentView->>ParentView: onInterceptTouchEvent ParentView->>ChildView: dispatchTouchEvent ChildView->>ChildView: onInterceptTouchEvent ChildView->>ChildView: onTouchEvent ChildView-->>ParentView: return onTouchEvent result ParentView->>ParentView: onTouchEvent ParentView-->>Activity: return onTouchEvent result Activity->>Activity: onTouchEvent

时序图描述了 Touch 事件在视图层次结构中的传递过程。事件首先从 Activity 开始,通过dispatchTouchEvent方法沿着视图层次结构自上而下地传递给子视图(隧道方式)。在这个过程中,每个视图都可以通过onInterceptTouchEvent方法对事件进行拦截。如果事件未被拦截,最终会传递到最内层的子视图。然后,在未被消费的情况下,事件会通过onTouchEvent方法沿着视图层次结构自下而上地回传给父视图(冒泡方式)。

3.3 简化实现

在 Android 中,Touch 事件的传递涉及到三个关键的方法:dispatchTouchEventonInterceptTouchEventonTouchEvent。它们的调用顺序和返回值决定了事件是如何在视图层次结构中传递的。下面我们用伪代码来分析如何实现隧道方式和冒泡方式。

假设我们有一个视图层次结构,从最外层的 Activity 到最内层的子视图,每一层视图都可以看作是一个节点,每个节点都有dispatchTouchEventonInterceptTouchEventonTouchEvent这三个方法。

java 复制代码
class View {
    boolean dispatchTouchEvent(MotionEvent event) {
        // 默认实现
        return onTouchEvent(event);
    }

    boolean onInterceptTouchEvent(MotionEvent event) {
        // 默认返回false,不拦截事件
        return false;
    }

    boolean onTouchEvent(MotionEvent event) {
        // 默认返回false,不消费事件
        return false;
    }
}
  1. 隧道方式(自上而下的事件传递)
java 复制代码
boolean dispatchTouchEvent(MotionEvent event) {
    boolean result = false;

    // 如果不拦截事件,传递给子视图
    if (!onInterceptTouchEvent(event)) {
        for (View child : children) {
            result = child.dispatchTouchEvent(event);
            if (result) {
                // 如果子视图消费了事件,停止传递
                break;
            }
        }
    }

    // 如果子视图没有消费事件,自己处理
    if (!result) {
        result = onTouchEvent(event);
    }

    return result;
}
  1. 冒泡方式(自下而上的事件回传)
java 复制代码
boolean onTouchEvent(MotionEvent event) {
    boolean result = false;

    // 如果有父视图,先让父视图处理
    if (parent != null) {
        result = parent.onTouchEvent(event);
    }

    // 如果父视图没有消费事件,自己处理
    if (!result) {
        // 处理事件...
        result = true;
    }

    return result;
}

这两段伪代码展示了隧道方式和冒泡方式的基本实现思路。在实际应用中,开发者可以根据需要重写这些方法,实现自定义的事件传递和处理逻辑。

四、ViewGroup 中的 dispatchTouchEvent 实现

在 Android 系统中,ViewGroup 对 dispatchTouchEvent 方法进行了重载,以实现更复杂的事件分发逻辑。以下是一些关键的代码片段:

java 复制代码
public boolean dispatchTouchEvent(MotionEvent ev) {
    boolean handled = false;
    // 调用onInterceptTouchEvent方法来判断是否需要拦截当前的触摸事件
    if (onInterceptTouchEvent(ev)) {
        // 如果需要拦截,就会将事件的动作设置为ACTION_CANCEL,并清除所有的触摸目标。
        ev.setAction(MotionEvent.ACTION_CANCEL);
        if (mFirstTouchTarget != null) {
            removePointersFromTouchTargets(0);
        }
        handled = true;
    } else {
        // 如果没有触摸目标,就会调用onTouchEvent方法来处理事件
        if (mFirstTouchTarget == null) {
            handled = onTouchEvent(ev);
        } else {
            // 如果有触摸目标,就会遍历所有的触摸目标,调用dispatchTransformedTouchEvent方法来分发事件。
            TouchTarget target = mFirstTouchTarget;
            while (target != null) {
                TouchTarget next = target.next;
                if (dispatchTransformedTouchEvent(ev, false, target.child, target.pointerIdBits)) {
                    handled = true;
                }
                target = next;
            }
        }
    }
    return handled;
}

首先,onInterceptTouchEvent 方法被调用,用于判断当前 ViewGroup 是否要拦截这个触摸事件。如果 onInterceptTouchEvent 返回 true,那么这个触摸事件将会被拦截,不再向下传递,同时触摸事件的 action 将会被设置为 ACTION_CANCEL,表示这个触摸事件被取消。

然后,如果没有拦截触摸事件,那么会检查是否有触摸目标(mFirstTouchTarget)。如果没有触摸目标,那么会直接调用 onTouchEvent 方法来处理这个触摸事件。如果有触摸目标,那么会遍历所有的触摸目标,并调用 dispatchTransformedTouchEvent 方法来分发触摸事件。这个过程会一直进行,直到找到能够处理这个触摸事件的 View 为止。

总的来说,ViewGroup 的 dispatchTouchEvent 方法通过调用 onInterceptTouchEvent 和 onTouchEvent 方法,实现了触摸事件的拦截和处理。这个过程涉及到了事件的分发、拦截和消费,是理解 Android 触摸事件分发机制的关键。

五、总结

通过本文的介绍,我们了解了 Android 下的 Touch 事件分发机制,包括事件分发的过程、涉及的方法以及 ViewGroup 中事件分发的实现。掌握这些知识点,可以帮助我们更好地进行事件处理和控件开发,提高应用的用户体验。

本文由mdnice多平台发布

相关推荐
whysqwhw13 分钟前
安卓图片性能优化技巧
android
风往哪边走23 分钟前
自定义底部筛选弹框
android
Yyyy4821 小时前
MyCAT基础概念
android
Android轮子哥2 小时前
尝试解决 Android 适配最后一公里
android
雨白3 小时前
OkHttp 源码解析:enqueue 非同步流程与 Dispatcher 调度
android
风往哪边走3 小时前
自定义仿日历组件弹框
android
没有了遇见3 小时前
Android 外接 U 盘开发实战:从权限到文件复制
android
Monkey-旭4 小时前
Android 文件存储机制全解析
android·文件存储·kolin
zhangphil5 小时前
Android Coil 3拦截器Interceptor计算单次请求耗时,Kotlin
android·kotlin
DokiDoki之父5 小时前
多线程—飞机大战排行榜功能(2.0版本)
android·java·开发语言