Android事件分发机制

移动开发 Android
在onTouch​方法中,可以编写自定义的触摸事件处理逻辑。根据MotionEvent对象的不同动作(如按下、移动、抬起等),可以执行相应的操作。最后,需要返回一个布尔值,表示是否已经处理了触摸事件。

事件分发机制

Android事件分发是指在Android系统中,当用户触摸屏幕或执行其他操作时,系统如何将这些事件传递给正确的视图或组件进行处理的过程。

Android事件分发遵循一种称为"事件分发机制"的规则,该机制由三个主要的阶段组成:触摸事件的捕获阶段、目标视图的处理阶段和冒泡阶段。

在触摸事件的捕获阶段,事件从顶层视图(如Activity)开始,逐级向下传递,直到找到最底层的子视图。在这个过程中,每个视图都有机会拦截事件,如果某个视图拦截了事件,则后续的视图将无法接收到该事件。

在目标视图的处理阶段,事件被传递给最底层的子视图,并由该视图进行处理。如果该视图没有处理事件,则事件将被传递给其父视图,直到找到能够处理事件的视图为止。

在冒泡阶段,事件从底层视图向上冒泡,直到达到顶层视图。在这个过程中,每个视图都有机会处理事件,如果某个视图处理了事件,则后续的视图将无法接收到该事件。

通过这种事件分发机制,Android系统能够准确地将用户的操作传递给正确的视图或组件进行处理,从而实现了用户与应用程序的交互。在实际开发中,我们可以通过重写视图的相关方法(如onTouchEvent())来自定义事件的处理逻辑,以满足特定的需求。

涉及的对象

  1. View:View是Android中的基本UI组件,它负责接收用户的输入事件(如点击、触摸等),并将事件传递给相应的处理方法。
  2. ViewGroup:ViewGroup是View的子类,它可以包含其他的View或ViewGroup。当一个事件发生在ViewGroup上时,它会遍历其子View,并将事件传递给合适的子View。
  3. MotionEvent:MotionEvent是Android中的事件对象,它封装了用户的触摸事件信息,包括触摸点的坐标、触摸的动作等。
  4. MotionEventCompat:MotionEventCompat是一个兼容性类,用于处理不同Android版本之间的触摸事件兼容性问题。
  5. GestureDetector:GestureDetector是Android提供的手势检测器,它可以识别用户的手势操作,如滑动、长按、双击等。
  6. OnTouchListener:OnTouchListener是一个接口,用于监听View的触摸事件。通过实现该接口,可以自定义触摸事件的处理逻辑。

以上是事件分发中的一些关键对象,它们共同协作,实现了Android中的事件分发机制。

MotionEvent

MotionEvent是Android中的一个类,用于处理与用户交互相关的事件,例如触摸屏幕、按下按钮等。它包含了一系列的常量和方法,用于获取事件的类型、坐标、时间等信息。通过监听MotionEvent,开发者可以实现对用户的触摸操作进行响应和处理。

以下是一些常用的MotionEvent方法:

  • getAction():获取触摸事件的动作类型,返回一个整数值。可以通过使用MotionEvent.ACTION_DOWN、MotionEvent.ACTION_MOVE、MotionEvent.ACTION_UP等常量来判断触摸事件的具体类型。
  • getDownTime():获取当屏幕刚被按下时的时间(毫秒),按下后移动此时间不变
  • getEventTime():获取MotionEvent所在的事件被激发的时间(毫秒)
  • getX()和getY():获取触摸事件的发生位置的横坐标和纵坐标。返回的是相对于触摸事件发生位置的坐标值。
  • getRawX()和getRawY():获取触摸事件的发生位置的原始横坐标和纵坐标。返回的是相对于屏幕的坐标值。
  • getPointerCount():获取触摸事件中手指的数量。
  • getPointerId(int pointerIndex):获取指定索引的手指的ID。
  • getPressure(int pointerIndex):获取指定索引的手指的压力值。
  • getHistorySize():获取触摸事件的历史记录的数量。
  • getHistoricalX(int pointerIndex, int pos)和getHistoricalY(int pointerIndex, int pos):获取指定索引的手指在指定历史记录位置的横坐标和纵坐标。

这些方法可以帮助开发者获取触摸事件的相关信息,并进行相应的处理。

MotionEvent#getAction()类型

MotionEvent#getAction()方法返回一个整数,表示当前触摸事件的类型。具体的类型有以下几种:

  • ACTION_DOWN:手指按下屏幕时触发的事件
  • ACTION_MOVE:手指在屏幕上滑动时触发的事件
  • ACTION_UP:手指离开屏幕时触发的事件
  • ACTION_CANCEL:触摸事件被取消时触发的事件
  • ACTION_OUTSIDE:超出区域

这些类型可以通过与MotionEvent类中定义的常量进行比较来判断当前触摸事件的类型。例如,可以使用以下代码来判断当前事件是否为按下事件:

if (event.getAction() == MotionEvent.ACTION_DOWN) {
    // 处理按下事件的逻辑
}

示例:手指触摸屏幕到离开屏幕事件走向

图片图片

事件分发对象间传递

在Android中,事件分发是通过View的事件分发机制来实现的。当用户触摸屏幕或者进行其他操作时,事件会从顶层的ViewGroup开始向下传递,直到找到合适的View来处理该事件。

Android中的事件分发涉及到以下几个对象:

  1. Activity:Activity是Android应用程序的一个组件,它负责管理用户界面和处理用户交互。当用户触摸屏幕或者进行其他操作时,事件首先会传递给当前显示的Activity。
  2. Window:Window是Activity的一个属性,它表示一个窗口,用于显示Activity的用户界面。事件会首先传递给Window,然后由Window负责将事件传递给对应的View。
  3. View:View是Android中的基本UI组件,用于构建用户界面。每个View都有一个事件分发的责任,它可以处理自己感兴趣的事件,也可以将事件传递给其他View。
  4. ViewGroup:ViewGroup是View的子类,用于管理其他View的布局和显示。当事件传递到ViewGroup时,它会遍历自己的子View,并将事件传递给合适的子View。

在事件分发过程中,每个对象都有机会处理事件。如果一个对象处理了事件,那么事件就会停止传递。如果一个对象没有处理事件,那么事件会继续向下传递,直到找到合适的处理者或者事件传递到最底层的View。

可以通过重写View的dispatchTouchEvent()方法来实现事件的分发和传递。在该方法中,可以根据需要调用super.dispatchTouchEvent()方法将事件传递给父View或者调用View的onTouchEvent()方法来处理事件。

Android中的事件分发是通过Activity、Window、View和ViewGroup等对象之间的协作来实现的。每个对象都有机会处理事件,通过合理地重写相关方法,可以实现事件的传递和处理。

事件分发机制解析

从上面的文章中我们得知Android事件分发机制的传递过程可以分为三个阶段:分发、拦截和处理。

  1. 分发阶段:事件首先由Activity或ViewGroup的dispatchTouchEvent()方法开始分发。在这个方法中,事件会被传递给当前ViewGroup的onInterceptTouchEvent()方法进行拦截判断。如果onInterceptTouchEvent()返回true,则表示当前ViewGroup拦截了事件,不再向下传递;如果返回false,则表示当前ViewGroup不拦截事件,会继续向下传递。
  2. 拦截阶段:如果当前ViewGroup不拦截事件,事件会继续向下传递给子View。子View的dispatchTouchEvent()方法会被调用,同样会经过onInterceptTouchEvent()方法的判断。如果子View拦截了事件,那么事件将不再向下传递给其他子View,而是交给子View的onTouchEvent()方法进行处理;如果子View不拦截事件,事件会继续向下传递。
  3. 处理阶段:如果事件没有被任何ViewGroup或View拦截,最终会传递给最底层的View的onTouchEvent()方法进行处理。在这个方法中,可以根据事件的类型(如触摸、滑动、点击等)进行相应的处理操作。

Android事件分发机制的传递过程是从上到下的递归过程,事件会依次经过父ViewGroup和子View的拦截判断,最终到达最底层的View进行处理。这个过程中,可以通过重写相关方法来实现事件的拦截和处理,从而实现自定义的交互逻辑。

Activity事件分发机制

Activity的事件分发机制是通过ViewGroup和View的层级关系来实现的。当用户触摸屏幕或者按下按键时,系统会将事件传递给当前显示的Activity的根布局ViewGroup,然后由ViewGroup负责将事件分发给各个子View进行处理。

具体的事件分发流程如下:

  1. 用户触摸屏幕或按下按键,事件首先传递给Activity的根布局ViewGroup。
  2. ViewGroup会调用自己的dispatchTouchEvent()方法来处理事件。在该方法中,ViewGroup会根据事件的类型(如触摸事件、按键事件等)进行相应的处理,例如判断是否需要拦截事件、是否需要消费事件等。
  3. 如果ViewGroup需要拦截事件,即认为自己应该处理该事件,那么事件就会停止向下传递,直接由ViewGroup来处理。
  4. 如果ViewGroup不需要拦截事件,那么事件会继续向下传递给子View。
  5. 子View会依次接收事件,并调用自己的dispatchTouchEvent()方法来处理事件。子View也会根据事件的类型进行相应的处理,例如判断是否需要拦截事件、是否需要消费事件等。
  6. 如果子View需要拦截事件,那么事件就会停止向下传递,直接由该子View来处理。
  7. 如果子View不需要拦截事件,那么事件会继续向下传递给下一个子View,直到事件被消费或者传递到最后一个子View。
  8. 如果事件最终没有被任何子View消费,那么事件会回到ViewGroup,ViewGroup会根据自身的情况来决定是否消费事件。
  9. 如果事件最终还是没有被消费,那么事件会继续向上层传递,直到被消费或者传递到最顶层的Activity。

通过这样的事件分发机制,Android系统可以实现对用户的触摸和按键事件进行灵活的处理,从而实现各种交互效果。

Activity源码:

public boolean dispatchTouchEvent(MotionEvent ev) {
    // 开始事件都是Dwon,一般第一次都会进入到onUserInteraction
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    // 若Window返回true,则会告诉Activity也返回true。true在所有touch代表着终止,不再继续往下一个事件传递了
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

下面看getWindow().superDispatchTouchEvent(ev); Window是个抽象类,唯一实现类为PhoneWindow,定位到PhoneWindow的superDispatchTouchEvent()。

PhoneWindow的源码:

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}

mDecor是DecorView类,DecorView是PhoneWindow类的一个内部类。同时DecorView也是整个Window中的最顶层View。

DecorView

DecorView是Android中的一个类,它是Android系统中的顶级视图,用于承载应用程序的用户界面。它是Android窗口系统的一部分,负责管理应用程序的窗口和布局。

DecorView是一个特殊的ViewGroup,它包含了应用程序的整个用户界面,包括状态栏、标题栏、内容区域等。它是Android应用程序的根视图,所有其他视图都是DecorView的子视图。

DecorView的主要作用是提供一个容器,用于放置应用程序的布局和控件。它还负责处理用户输入事件,如触摸、滑动等,并将其传递给相应的子视图进行处理。

在Android开发中,我们通常不直接操作DecorView,而是通过Activity或Fragment来管理和操作应用程序的用户界面。DecorView在内部被Activity或Fragment自动创建和管理,开发者只需要关注布局和控件的设计和交互逻辑即可。

DecorView是Android应用程序的根视图,负责承载应用程序的用户界面,并提供容器和事件处理功能。

DecorView是一个特殊的ViewGroup,分发处理同ViewGroup,下面看ViewGroup的superDispatchTouchEvent()

public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
}

整个Activity事件分发过程如下图:

图片图片

ViewGroup事件分发机制

ViewGroup是一种特殊的View,它可以包含其他的View或者ViewGroup。当用户进行触摸操作时,事件会被传递给ViewGroup,并由ViewGroup负责将事件分发给其子View或者子ViewGroup。

ViewGroup的事件分发机制主要包括以下几过程:

  1. 事件的传递:当用户触摸屏幕时,事件会首先传递给最顶层的ViewGroup,即Activity的根布局。然后,ViewGroup会根据触摸事件的坐标位置,确定哪个子View或者子ViewGroup应该接收该事件。
  2. 事件的拦截:在确定了接收事件的子View或者子ViewGroup后,ViewGroup会调用该子View或者子ViewGroup的dispatchTouchEvent()方法,将事件传递给它们。在这个过程中,如果父ViewGroup需要拦截事件,可以通过重写onInterceptTouchEvent()方法来实现。如果父ViewGroup拦截了事件,那么该事件将不会传递给子View或者子ViewGroup,而是由父ViewGroup来处理。
  3. 事件的处理:当事件传递到子View或者子ViewGroup时,它们会调用自己的dispatchTouchEvent()方法来处理事件。在这个方法中,子View或者子ViewGroup可以根据自己的需求来处理事件,例如响应点击、滑动等操作。如果子View或者子ViewGroup不消费事件,那么事件会继续传递给父ViewGroup,直到事件被消费或者传递到最顶层的ViewGroup。

ViewGroup的事件分发机制是通过事件的传递、拦截和处理来实现的。通过重写dispatchTouchEvent()和onInterceptTouchEvent()方法,可以对事件进行自定义的处理。这种机制可以保证事件在ViewGroup及其子View或者子ViewGroup之间的正确传递和处理。

整个ViewGroup分发过程如下图:

图片图片

View事件分发机制

View事件分发机制主要包括三个关键方法:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent。

  1. dispatchTouchEvent:该方法是ViewGroup类中的一个关键方法,用于分发触摸事件。当一个触摸事件发生时,系统会首先将该事件传递给顶层的ViewGroup,然后由ViewGroup根据一定的规则将事件传递给子View进行处理。在dispatchTouchEvent方法中,系统会根据事件类型和触摸位置等信息,决定将事件传递给哪个子View进行处理。
  2. onInterceptTouchEvent:该方法也是ViewGroup类中的一个关键方法,用于拦截触摸事件。当一个触摸事件传递给ViewGroup后,ViewGroup会先调用onInterceptTouchEvent方法来判断是否需要拦截该事件。如果onInterceptTouchEvent方法返回true,则表示ViewGroup会拦截该事件,并将事件传递给自己的onTouchEvent方法进行处理;如果返回false,则表示ViewGroup不会拦截该事件,会将事件继续传递给子View进行处理。
  3. onTouchEvent:该方法是View类中的一个关键方法,用于处理触摸事件。当一个触摸事件传递到View时,View会调用自己的onTouchEvent方法来处理该事件。在onTouchEvent方法中,可以根据事件类型进行相应的处理,例如处理点击事件、滑动事件等。

View事件分发机制的流程如下:

  1. 当用户触摸屏幕时,系统会将触摸事件传递给顶层的ViewGroup。
  2. ViewGroup会调用dispatchTouchEvent方法来分发触摸事件。
  3. 在dispatchTouchEvent方法中,ViewGroup会根据一定的规则将事件传递给子View进行处理。
  4. 子View会先调用onInterceptTouchEvent方法来判断是否需要拦截该事件。
  5. 如果onInterceptTouchEvent方法返回true,则表示ViewGroup会拦截该事件,并将事件传递给自己的onTouchEvent方法进行处理。
  6. 如果onInterceptTouchEvent方法返回false,则表示ViewGroup不会拦截该事件,会将事件继续传递给子View进行处理。
  7. 子View会调用自己的onTouchEvent方法来处理触摸事件。

View#dispatchTouchEvent源码:

public boolean dispatchTouchEvent(MotionEvent event) {
    ...
    if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) {
        result = true;
    }
    ...
}

只有当4个条件都为真才返回true,否则执行onTouchEvent(),下面对这4个条件逐个分析:

  • li != null:li即是ListenerInfo,ListenerInfo是封装了所有事件,所以只要赋值任一事件,这个都不可能会为null。
  • mOnTouchListener != null:mOnTouchListener变量在View.setOnTouchListener()方法里赋值,即只要我们给控件注册了Touch事件,mOnTouchListener就不为空。
  • (mViewFlags & ENABLED_MASK) == ENABLED:判断当前点击的控件是否enable,由于View默认enable,故该条件为true。
  • mOnTouchListener.onTouch(this, event):控件注册touch事件时重写的onTouch()方法。

若在setOnTouchListener返回true,就会满足以上4个条件,并且返回了true,从而使得View.dispatchTouchEvent()直接返回true,事件分发结束,不会执行onTouchEvent(event)。

View#onTouchEvent(event)源码:

public boolean onTouchEvent(MotionEvent event) {
    ...
    // clickable代表该控件是否可点击,可点击就进入下面条件判断
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            // 1. 当前的事件 = 抬起View
            case MotionEvent.ACTION_UP:
                // 经过种种判断,此处省略
                ........
                if (!focusTaken) {
                    // 执行performClick()
                    if (mPerformClick == null) {
                        mPerformClick = new PerformClick();
                    }
                    if (!post(mPerformClick)) {
                        performClickInternal();
                    }
                }
                break;
                // 2. 当前的事件 = 按下View
                case MotionEvent.ACTION_DOWN:
                    // 经过种种判断,此处省略
                    break;
                // 3. 当前的事件 = 结束事件(非人为原因)
                case MotionEvent.ACTION_CANCEL:
                    // 经过种种判断,此处省略
                    break;
                // 4. 当前的事件 = 滑动View
                case MotionEvent.ACTION_MOVE:
                    // 经过种种判断,此处省略
                    break;
            }
            // 若该控件可点击,就一定返回true
            return true;
        }
    // 若该控件不可点击,就一定返回false
    return false;
    ...
}

整个View分发过程如下图:

图片图片

总结

图片图片

dispatchTouchEvent

dispatchTouchEvent用于分发触摸事件。它是ViewGroup类中的一个方法,用于将触摸事件传递给子View或处理自身的触摸事件。

触摸事件的传递是通过触摸事件分发机制来实现的。当用户触摸屏幕时,系统会将触摸事件传递给顶层的ViewGroup,然后由ViewGroup负责将触摸事件传递给子View或处理自身的触摸事件。

dispatchTouchEvent方法的作用是将触摸事件分发给子View或处理自身的触摸事件。它会根据触摸事件的类型和位置来确定是将触摸事件传递给子View,还是处理自身的触摸事件。

在dispatchTouchEvent方法中,会依次调用onInterceptTouchEvent方法和onTouchEvent方法来判断是否拦截触摸事件和处理触摸事件。如果onInterceptTouchEvent方法返回true,则表示拦截触摸事件,不再向子View传递触摸事件;如果onTouchEvent方法返回true,则表示处理了触摸事件,不再向子View传递触摸事件。

onTouchEvent

onTouchEvent用于处理触摸事件。它是View类的一个成员方法,可以被重写以实现自定义的触摸事件处理逻辑。

触摸事件包括按下(ACTION_DOWN)、移动(ACTION_MOVE)、抬起(ACTION_UP)等多个动作。当用户触摸屏幕时,系统会将触摸事件传递给相应的View,并调用该View的onTouchEvent方法来处理事件。

在onTouchEvent方法中,可以根据不同的触摸动作进行相应的处理,例如根据触摸位置进行绘制、处理滑动事件、处理点击事件等。可以通过重写onTouchEvent方法来实现自定义的触摸交互效果。

重写onTouchEvent方法来处理触摸事件:

@Override
public boolean onTouchEvent(MotionEvent event) {
    int action = event.getAction();
    switch (action) {
        case MotionEvent.ACTION_DOWN:
            // 处理按下事件
            break;
        case MotionEvent.ACTION_MOVE:
            // 处理移动事件
            break;
        case MotionEvent.ACTION_UP:
            // 处理抬起事件
            break;
    }
    return true;
}

需要注意的是,onTouchEvent方法的返回值为boolean类型。如果返回true,表示已经处理了该触摸事件,不再向其他View传递;如果返回false,则会将该触摸事件传递给父View或其他相关的View进行处理。

onInterceptTouchEvent

onInterceptTouchEvent用于拦截触摸事件。它通常用于父容器对子View的触摸事件进行拦截和处理。

触摸事件是由屏幕上的触摸点产生的,包括按下、移动和抬起等动作。当一个触摸事件发生时,系统会将该事件传递给最上层的View,并通过dispatchTouchEvent方法进行分发。在分发过程中,如果父容器的onInterceptTouchEvent方法返回true,则表示父容器要拦截该事件,不再将事件传递给子View;如果返回false,则表示父容器不拦截该事件,继续将事件传递给子View。

onInterceptTouchEvent方法的返回值决定了是否拦截触摸事件,它有三种可能的返回值:

  • 返回true:表示父容器要拦截触摸事件,不再传递给子View。
  • 返回false:表示父容器不拦截触摸事件,继续传递给子View。
  • 返回super.onInterceptTouchEvent(event):表示父容器不对触摸事件进行拦截,继续按照默认的方式处理。

通过在onInterceptTouchEvent方法中对触摸事件进行处理,我们可以实现一些特定的触摸事件逻辑,例如滑动冲突处理、多指触摸事件的处理等。

setOnTouchListener

setOnTouchListener是一个用于设置触摸事件监听器的方法,用于对触摸事件进行处理。

使用setOnTouchListener方法,可以为一个控件(如Button、ImageView等)设置一个触摸事件监听器。当用户触摸该控件时,触摸事件监听器会被触发,并执行相应的操作。

示例代码:

button.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        // 处理触摸事件的逻辑代码
        return true; // 返回true表示已经处理了触摸事件,false表示未处理
    }
});

button是要设置触摸事件监听器的视图对象。setOnTouchListener方法接受一个View.OnTouchListener对象作为参数,该对象实现了onTouch方法,用于处理触摸事件。

在onTouch方法中,可以编写自定义的触摸事件处理逻辑。根据MotionEvent对象的不同动作(如按下、移动、抬起等),可以执行相应的操作。最后,需要返回一个布尔值,表示是否已经处理了触摸事件。

使用setOnTouchListener方法可以实现各种触摸事件的处理,例如拖动、缩放、滑动等。根据具体需求,可以在onTouch方法中编写相应的代码逻辑。

责任编辑:武晓燕 来源: 沐雨花飞蝶
相关推荐

2016-12-08 10:19:18

Android事件分发机制

2017-02-21 12:20:20

Android事件分发机制实例解析

2021-08-17 13:41:11

AndroidView事件

2017-07-20 16:55:56

Android事件响应View源码分析

2010-08-06 10:03:42

Flex事件

2017-03-14 13:51:23

AndroidView事件分发和处理

2010-08-06 10:24:56

Flex事件分发

2013-04-24 11:15:56

Android开发Touch事件传递机制

2012-12-28 14:53:34

Android开发初始化窗体事件

2024-07-01 08:27:05

KeyAndroid按键事件

2016-12-12 14:55:01

AndroidAndroid Vie

2021-08-20 09:48:07

鸿蒙HarmonyOS应用

2021-08-11 14:29:20

鸿蒙HarmonyOS应用

2010-08-12 15:35:44

Flex事件机制

2010-08-06 09:45:50

Flex事件机制

2010-08-04 13:52:53

Flex事件机制

2010-08-04 14:07:59

Flex事件机制

2010-07-29 10:33:59

Flex键盘事件

2011-07-01 14:14:34

Qt 事件

2011-07-01 14:20:59

Qt 事件
点赞
收藏

51CTO技术栈公众号