事件分发机制
Android事件分发是指在Android系统中,当用户触摸屏幕或执行其他操作时,系统如何将这些事件传递给正确的视图或组件进行处理的过程。
Android事件分发遵循一种称为"事件分发机制"的规则,该机制由三个主要的阶段组成:触摸事件的捕获阶段、目标视图的处理阶段和冒泡阶段。
在触摸事件的捕获阶段,事件从顶层视图(如Activity)开始,逐级向下传递,直到找到最底层的子视图。在这个过程中,每个视图都有机会拦截事件,如果某个视图拦截了事件,则后续的视图将无法接收到该事件。
在目标视图的处理阶段,事件被传递给最底层的子视图,并由该视图进行处理。如果该视图没有处理事件,则事件将被传递给其父视图,直到找到能够处理事件的视图为止。
在冒泡阶段,事件从底层视图向上冒泡,直到达到顶层视图。在这个过程中,每个视图都有机会处理事件,如果某个视图处理了事件,则后续的视图将无法接收到该事件。
通过这种事件分发机制,Android系统能够准确地将用户的操作传递给正确的视图或组件进行处理,从而实现了用户与应用程序的交互。在实际开发中,我们可以通过重写视图的相关方法(如onTouchEvent())来自定义事件的处理逻辑,以满足特定的需求。
涉及的对象
- View:View是Android中的基本UI组件,它负责接收用户的输入事件(如点击、触摸等),并将事件传递给相应的处理方法。
- ViewGroup:ViewGroup是View的子类,它可以包含其他的View或ViewGroup。当一个事件发生在ViewGroup上时,它会遍历其子View,并将事件传递给合适的子View。
- MotionEvent:MotionEvent是Android中的事件对象,它封装了用户的触摸事件信息,包括触摸点的坐标、触摸的动作等。
- MotionEventCompat:MotionEventCompat是一个兼容性类,用于处理不同Android版本之间的触摸事件兼容性问题。
- GestureDetector:GestureDetector是Android提供的手势检测器,它可以识别用户的手势操作,如滑动、长按、双击等。
- 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中的事件分发涉及到以下几个对象:
- Activity:Activity是Android应用程序的一个组件,它负责管理用户界面和处理用户交互。当用户触摸屏幕或者进行其他操作时,事件首先会传递给当前显示的Activity。
- Window:Window是Activity的一个属性,它表示一个窗口,用于显示Activity的用户界面。事件会首先传递给Window,然后由Window负责将事件传递给对应的View。
- View:View是Android中的基本UI组件,用于构建用户界面。每个View都有一个事件分发的责任,它可以处理自己感兴趣的事件,也可以将事件传递给其他View。
- ViewGroup:ViewGroup是View的子类,用于管理其他View的布局和显示。当事件传递到ViewGroup时,它会遍历自己的子View,并将事件传递给合适的子View。
在事件分发过程中,每个对象都有机会处理事件。如果一个对象处理了事件,那么事件就会停止传递。如果一个对象没有处理事件,那么事件会继续向下传递,直到找到合适的处理者或者事件传递到最底层的View。
可以通过重写View的dispatchTouchEvent()方法来实现事件的分发和传递。在该方法中,可以根据需要调用super.dispatchTouchEvent()方法将事件传递给父View或者调用View的onTouchEvent()方法来处理事件。
Android中的事件分发是通过Activity、Window、View和ViewGroup等对象之间的协作来实现的。每个对象都有机会处理事件,通过合理地重写相关方法,可以实现事件的传递和处理。
事件分发机制解析
从上面的文章中我们得知Android事件分发机制的传递过程可以分为三个阶段:分发、拦截和处理。
- 分发阶段:事件首先由Activity或ViewGroup的dispatchTouchEvent()方法开始分发。在这个方法中,事件会被传递给当前ViewGroup的onInterceptTouchEvent()方法进行拦截判断。如果onInterceptTouchEvent()返回true,则表示当前ViewGroup拦截了事件,不再向下传递;如果返回false,则表示当前ViewGroup不拦截事件,会继续向下传递。
- 拦截阶段:如果当前ViewGroup不拦截事件,事件会继续向下传递给子View。子View的dispatchTouchEvent()方法会被调用,同样会经过onInterceptTouchEvent()方法的判断。如果子View拦截了事件,那么事件将不再向下传递给其他子View,而是交给子View的onTouchEvent()方法进行处理;如果子View不拦截事件,事件会继续向下传递。
- 处理阶段:如果事件没有被任何ViewGroup或View拦截,最终会传递给最底层的View的onTouchEvent()方法进行处理。在这个方法中,可以根据事件的类型(如触摸、滑动、点击等)进行相应的处理操作。
Android事件分发机制的传递过程是从上到下的递归过程,事件会依次经过父ViewGroup和子View的拦截判断,最终到达最底层的View进行处理。这个过程中,可以通过重写相关方法来实现事件的拦截和处理,从而实现自定义的交互逻辑。
Activity事件分发机制
Activity的事件分发机制是通过ViewGroup和View的层级关系来实现的。当用户触摸屏幕或者按下按键时,系统会将事件传递给当前显示的Activity的根布局ViewGroup,然后由ViewGroup负责将事件分发给各个子View进行处理。
具体的事件分发流程如下:
- 用户触摸屏幕或按下按键,事件首先传递给Activity的根布局ViewGroup。
- ViewGroup会调用自己的dispatchTouchEvent()方法来处理事件。在该方法中,ViewGroup会根据事件的类型(如触摸事件、按键事件等)进行相应的处理,例如判断是否需要拦截事件、是否需要消费事件等。
- 如果ViewGroup需要拦截事件,即认为自己应该处理该事件,那么事件就会停止向下传递,直接由ViewGroup来处理。
- 如果ViewGroup不需要拦截事件,那么事件会继续向下传递给子View。
- 子View会依次接收事件,并调用自己的dispatchTouchEvent()方法来处理事件。子View也会根据事件的类型进行相应的处理,例如判断是否需要拦截事件、是否需要消费事件等。
- 如果子View需要拦截事件,那么事件就会停止向下传递,直接由该子View来处理。
- 如果子View不需要拦截事件,那么事件会继续向下传递给下一个子View,直到事件被消费或者传递到最后一个子View。
- 如果事件最终没有被任何子View消费,那么事件会回到ViewGroup,ViewGroup会根据自身的情况来决定是否消费事件。
- 如果事件最终还是没有被消费,那么事件会继续向上层传递,直到被消费或者传递到最顶层的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的事件分发机制主要包括以下几过程:
- 事件的传递:当用户触摸屏幕时,事件会首先传递给最顶层的ViewGroup,即Activity的根布局。然后,ViewGroup会根据触摸事件的坐标位置,确定哪个子View或者子ViewGroup应该接收该事件。
- 事件的拦截:在确定了接收事件的子View或者子ViewGroup后,ViewGroup会调用该子View或者子ViewGroup的dispatchTouchEvent()方法,将事件传递给它们。在这个过程中,如果父ViewGroup需要拦截事件,可以通过重写onInterceptTouchEvent()方法来实现。如果父ViewGroup拦截了事件,那么该事件将不会传递给子View或者子ViewGroup,而是由父ViewGroup来处理。
- 事件的处理:当事件传递到子View或者子ViewGroup时,它们会调用自己的dispatchTouchEvent()方法来处理事件。在这个方法中,子View或者子ViewGroup可以根据自己的需求来处理事件,例如响应点击、滑动等操作。如果子View或者子ViewGroup不消费事件,那么事件会继续传递给父ViewGroup,直到事件被消费或者传递到最顶层的ViewGroup。
ViewGroup的事件分发机制是通过事件的传递、拦截和处理来实现的。通过重写dispatchTouchEvent()和onInterceptTouchEvent()方法,可以对事件进行自定义的处理。这种机制可以保证事件在ViewGroup及其子View或者子ViewGroup之间的正确传递和处理。
整个ViewGroup分发过程如下图:
图片
View事件分发机制
View事件分发机制主要包括三个关键方法:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent。
- dispatchTouchEvent:该方法是ViewGroup类中的一个关键方法,用于分发触摸事件。当一个触摸事件发生时,系统会首先将该事件传递给顶层的ViewGroup,然后由ViewGroup根据一定的规则将事件传递给子View进行处理。在dispatchTouchEvent方法中,系统会根据事件类型和触摸位置等信息,决定将事件传递给哪个子View进行处理。
- onInterceptTouchEvent:该方法也是ViewGroup类中的一个关键方法,用于拦截触摸事件。当一个触摸事件传递给ViewGroup后,ViewGroup会先调用onInterceptTouchEvent方法来判断是否需要拦截该事件。如果onInterceptTouchEvent方法返回true,则表示ViewGroup会拦截该事件,并将事件传递给自己的onTouchEvent方法进行处理;如果返回false,则表示ViewGroup不会拦截该事件,会将事件继续传递给子View进行处理。
- onTouchEvent:该方法是View类中的一个关键方法,用于处理触摸事件。当一个触摸事件传递到View时,View会调用自己的onTouchEvent方法来处理该事件。在onTouchEvent方法中,可以根据事件类型进行相应的处理,例如处理点击事件、滑动事件等。
View事件分发机制的流程如下:
- 当用户触摸屏幕时,系统会将触摸事件传递给顶层的ViewGroup。
- ViewGroup会调用dispatchTouchEvent方法来分发触摸事件。
- 在dispatchTouchEvent方法中,ViewGroup会根据一定的规则将事件传递给子View进行处理。
- 子View会先调用onInterceptTouchEvent方法来判断是否需要拦截该事件。
- 如果onInterceptTouchEvent方法返回true,则表示ViewGroup会拦截该事件,并将事件传递给自己的onTouchEvent方法进行处理。
- 如果onInterceptTouchEvent方法返回false,则表示ViewGroup不会拦截该事件,会将事件继续传递给子View进行处理。
- 子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方法中编写相应的代码逻辑。