在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 时序图

Activity ParentView ChildView dispatchTouchEvent onInterceptTouchEvent dispatchTouchEvent onInterceptTouchEvent onTouchEvent return onTouchEvent result onTouchEvent return onTouchEvent result onTouchEvent Activity ParentView ChildView

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

3.3 简化实现

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

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

class View {
    boolean dispatchTouchEvent(MotionEvent event) {
        // 默认实现
        return onTouchEvent(event);
    }

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

    boolean onTouchEvent(MotionEvent event) {
        // 默认返回false,不消费事件
        return false;
    }
}
  1. 隧道方式(自上而下的事件传递)
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. 冒泡方式(自下而上的事件回传)
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方法进行了重载,以实现更复杂的事件分发逻辑。以下是一些关键的代码片段:

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中事件分发的实现。掌握这些知识点,可以帮助我们更好地进行事件处理和控件开发,提高应用的用户体验。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐