Android中的事件分发机制

1. 一个小问题引发的思考

2. 通过源码探索View中的事件分发机制

3.通过源码探索ViewGroup的事件分发机制

最近的一个项目中涉及,布局为一个RelativeLayout包含了一个EditText和一个Button,当点击EditText时,弹出软键盘,点击RelativeLayout中除了EditText和Button之外其它的地方时,收起软键盘。

实现起来很简单,为EditText和RelativeLayout分别注册一个onTouch事件,为Button注册一个click事件,一切Ok~ 但在业务层简单的实现背后,却给我带来了一些疑问:

1. EditText作为RelativeLayout的子元素,为何它的onTouch事件没有触发父元素(RelativeLayout)的onTouch事件,父子节点的同一事件的事件分发逻辑是怎样呢?

2. onClick事件和onTouch事件有和关联呢?

3. 我们既可以为控件注册onTouch事件(setOnTouchLisnter),也可以自定义控件实现onTouchEvent方法,onTouch方法和onTouchEvent方法有何区别呢,它们的执行时机是什么?

带着这三个疑问,我踏上了google, 度娘,以及Android源代码探索的不归路~

Android中,所有的操作类型事件都由如下三个部分作为基础:

  • 按下(ACTION_DOWN)
  • 移动(ACTION_MOVE)
  • 抬起(ACTION_UP)

这三部分都寄生于onTouch事件中,由MontionEvent类中定义的三个常量进行区分。

Android中与Touch事件相关的方法为:


Touch事件相关方法


    方法功能    


    ViewGroup    


View(子View


    Activity    


public boolean dispatchTouchEvnet(MotionEvent ev)


事件分发


Yes


Yes


Yes


public boolean onInterceptTouchEvent(MotionEvent ev)


事件拦截


Yes


No


No


public boolean onTouchEvent(MotionEvent ev)


事件响应


Yes


Yes


Yes

分发逻辑:整个Touch事件的分发其实是以Activity的dispatchTouchEvent作为起点,将事件传递给最外层ViewGroup的dispatchTouchEvent方法,再由该ViewGroup进行递归分发,直至叶子节点View的dispatchTouchEvent方法中。

为了证明这一逻辑,我们对代码一层层地分析,首先看下View中的dispatchTouchEvent方法:

View.Java

[java] view
plain
 copy

  1. public boolean dispatchTouchEvent(MotionEvent event) {
  2. if (mInputEventConsistencyVerifier != null) {
  3. mInputEventConsistencyVerifier.onTouchEvent(event, 0);
  4. }
  5. if (onFilterTouchEventForSecurity(event)) {
  6. //noinspection SimplifiableIfStatement
  7. ListenerInfo li = mListenerInfo;
  8. if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
  9. && li.mOnTouchListener.onTouch(this, event)) {
  10. return true;
  11. }
  12. if (onTouchEvent(event)) {
  13. return true;
  14. }
  15. }
  16. if (mInputEventConsistencyVerifier != null) {
  17. mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
  18. }
  19. return false;
  20. }

整个程序中有最关键的两处判断(第9行和14行),在第9行的if条件中,如果li.mOnTouchListener != null,(mViewFlags & ENABLED_MASK) == ENABLED 和 mOnTouchListener.onTouch(this, event) == true这三个条件同时满足时,就直接返回true。

第一个条件:mOnTouchListener在哪里赋值呢?CRTL+F搜索,发现了View.java中的如下方法:

View.java

[java] view
plain
 copy

  1. public void setOnTouchListener(OnTouchListener l) {
  2. getListenerInfo().mOnTouchListener = l;
  3. }

太眼熟了,这不正是我们平时注册onTouch事件所使用的函数么,这下明白了,这要注册了onTouch事件,mOnTouchListener就一定不为空。好了,继续往下看。

第二个条件:(mViewFlags & ENABLED_MASK) == ENABLED 判断当前的View是否为ENABLED,一般控件默认都是ENABLED,因此这个条件也成立。

第三个条件:mOnTouchListener.onTouch(this, event) == true,即我们注册的onTouch方法中,如果返回true,就会使三个条件都成立,这样,就不会继续接下来的判断了。

在第14行的If判断中,只是简单的判断了onTouchEvent方法的返回是否为true。

上述短短的几行代码解开了我的第三个疑问,下面来总结一下:

  1. 在View的dispatchTouchEvent方法中,最先执行的是onTouch方法,只有当onTouch方法返回false,才有机会去执行onTouchEvent方法。
  2. touch事件有层级传递机制,当我们为一个view注册touch事件,就会触发一系列的ACTION_DOWN,ACTION_MOVE_ACTION_UP,如果在执行ACTION_DOWN的时候返回false,则后面的ACTION_MOVE和ACTION_UP都得不到执行,即使用dispatchTouchEvent进行分发的时候,只有前一个action返回true,后一个action才能得到执行。

在上述的的第二个结论中,我们可知,只有当onTouch方法或者onTouchEvent都返回true,才能继续后面的操作,onTouch方法的返回一般是我们自己控制的,而onTouchEvent方法,若不自定义控件,则往往会使用它的默认实现(false),button是return true

  1. 关于Android中的事件机制,用到的地方还是很多的,并且这个知识点还真有点复杂。

    在写这篇文章前,网上看了不少博文,有的写的感觉挺不错的。只是当时感觉好像理解了,事后又很容易忘。现在自己也系统整理下吧。

    Android中的事件在表现形式上有很多,如onTouch、onClick和onLongClick等,在具体微观上的表现形势有action_down、action_move和action_up等。

    无论哪种事件表现类型,首先都是基于事件的传递模型。其实Android中的事件传递有点类似于JS中事件传递模型。都是基于先捕获然后冒泡的形式。

    在捕获阶段,事件先由外部的View接收,然后传递给其内层的View,依次传递到更够接收此事件的最小View单元,完成事件捕获过程;

    在冒泡阶段,事件则从事件源的最小View单元开始,依次向外冒泡,将事件对层传递。

    事件的捕获和冒泡是整个事件的传递流程,但是在实际的传递过程中,Android中则表现的相对复杂。

    主要表现在可以控制每层事件是否继续传递(由事件分发和事件拦截协同进行),以及事件的具体消费(由事件消响应进行,但需要注意的是,事件分发自身也具有事件消费能力)。

    也就是本文提及的事件分发、拦截和响应。

    Android中不同的控件所具有的事件分发、拦截和响应稍有不同,主要表现在Activity本身不具有事件拦截,不是ViewGroup的最小view单元不具有事件拦截(因为它没有自己的子View)

  2. 事件分发:public boolean dispatchTouchEvent(MotionEvent ev)

    当有监听到事件时,首先由Activity的捕获到,进入事件分发处理流程。无论是Activity还是View,如前文所说,事件分发自身也具有消费能力,

    如果事件分发返回true,表示改事件在本层不再进行分发且已经在事件分发自身中被消费了。至此,事件已经完结。如果你不想Activity中的任何控件具有任何的事件消费能力,

    最简答的方法可以重写此Activity的dispatchTouchEvent方法,直接返回true就ok(一开始就返回true)。

    return false: 表明事件不会被进行分发。事件会以冒泡的方式被传递给上层的view或activity的onTouchEvent方法进行消费掉  。

    当然了,如果本层控件已经是Activity(return true),那么事件将被系统消费或处理。

    如果事件分发返回系统默认的 super.dispatchTouchEvent(ev),表明该事件将会被分发。此时当前View的onIntercepterTouchEvent方法会捕获该事件,判断需不需要进行事件的拦截

dispatchTouchEvent是处理触摸事件分发,事件(多数情况)是从Activity的dispatchTouchEvent开始的。执行

super.dispatchTouchEvent(ev),事件向下分发(如果本层控件是Activity,由于其没有事件拦截,因此将直接将事件传递到子View,并交给子View的事件分发进行处理)

在activity里面:

  1. public boolean dispatchTouchEvent(MotionEvent ev) {
  2. //如果是按下状态就调用onUserInteraction()方法,onUserInteraction()方法
  3. //是个空的方法, 我们直接跳过这里看下面的实现
  4. if (ev.getAction() == MotionEvent.ACTION_DOWN) {
  5. onUserInteraction();
  6. }
  7. if (getWindow().superDispatchTouchEvent(ev)) {
  8. return true;
  9. }
  10. //getWindow().superDispatchTouchEvent(ev)返回false,这个事件就交给Activity
  11. //来处理, Activity的onTouchEvent()方法直接返回了false
  12. return onTouchEvent(ev);

从activity->PhoneWindow->DecorView

我们看到最顶层就是PhoneWindow$DecorView,接着DecorView下面有一个LinearLayout, LinearLayout下面有两个FrameLayout

上面那个FrameLayout是用来显示标题栏的,这个Demo中是一个TextView,当然我们还可以定制我们的标题栏,利用getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE,R.layout.XXX); xxx就是我们自定义标题栏的布局XML文件

下面的FrameLayout是用来装载ContentView的,也就是我们在Activity中利用setContentView()方法设置的View,现在我们知道了,原来我们利用setContentView()设置Activity的View的外面还嵌套了这么多的东西

结论1:在activity里dispatchTouchEvent,如果走到子view返回true,getWindow().superDispatchTouchEvent(ev)才会返回true,activity才会返回true,接着后续的move
up才会往下走,如果getWindow().superDispatchTouchEvent(ev)返回false,导致Activity的onTouchEvent()方法直接返回了false,后续的move
up不会往下走,这也是为什么从最里层传回true,后续的move up才会往下走,如果一开始就把dispatchTouchEvent返回true,并没有往下走,activity就会消费onTouchEvent

事件拦截:public boolean onInterceptTouchEvent(MotionEvent ev)

onInterceptTouchEvent是ViewGroup提供的方法,默认返回false

如果 onInterceptTouchEvent 返回 true,则表示将事件进行拦截,并将拦截到的事件交由本层控件 的 onTouchEvent 进行处理;

返回值:true

自己处理,不需要继续下传

事件会传递到自己的onTouchEvent()

Down事件在onInterceptTouchEvent()后返回true,则传递到onTouchEvent,

当其返回true时,动作序列的后续事件不会再通过onInterceptTouchEvent了,

而是在dispatchTouchEvent中直接传递于onTouchEvent

如果返回结果是false;则表示不对事件进行拦截,事件得以成功分发到子View。并由子View的dispatchTouchEvent进行处理。

返回值:false

自己无法完全处理,或者不能处理,继续下传

传递到下一个view的dispatchTouchEvent()

return super.inInterceptTouchEvent(ev):默认拦截方式,和return true一样。该事件会被拦截,将该事件交给当前view的onTouchEvent方法进行处理。(这里需要有一点说明,当有两个view。A
view中有一个B view.点击A.A中如果onInterceptTouchEvent()返回super.interceptTouchEvent(ev),则事件将会被A进行拦截,交给A的onTouchEvent()进行处理,如果点击的是B,A中如果onInterceptTouchEvent()返回super.interceptTouchEvent(ev),则事件将不会被拦截,会被分发到子控件中)

public boolean onTouchEvent(MotionEvent event)

return false:表明没有消费该事件,事件将会以冒泡的方式一直被传递到上层的view或Activity中的onTouchEvent事件处理。如果最上层的view或Activity中的onTouchEvent还是返回false。则该事件将消失。接下来来的一系列事件都将会直接被上层的onTouchEvent方法捕获

return true: 表明消费了该事件,事件到此结束。

return super.onTouchEvent(event):默认情况,和return false一样。

由于onInterceptTouchEvent()的机制比较复杂,上面的说明写的也比较复杂,总结一下,基本的规则是:

1.       down事件首先会传递到onInterceptTouchEvent()方法

2.       如果该ViewGroup的onInterceptTouchEvent()在接收到down事件处理完成之后return false,那么后续的move, up等事件将继续会先传递给该ViewGroup,之后才和down事件一样传递给最终的目标view的onTouchEvent()处理。

3.       如果该ViewGroup的onInterceptTouchEvent()在接收到down事件处理完成之后return true,那么后续的move, up等事件将不再传递给onInterceptTouchEvent(),而是和down事件一样传递给该ViewGroup的onTouchEvent()处理,注意,目标view将接收不到任何事件。

4.       如果最终需要处理事件的view的onTouchEvent()返回了false,那么该事件将被传递至其上一层次的view的onTouchEvent()处理。

5.       如果最终需要处理事件的view 的onTouchEvent()返回了true,那么后续事件将可以继续传递给该view的onTouchEvent()处理。

验证:

MainActivity FatherView ChildView中几个方法都返回super.****TouchEvent(ev)

分析:

  1. 当点击屏幕。MainActivity 中的dispatchTouchEvent方法先执行,打印MainActivity-dispatchTouchEvent-->ACTION_DOWN
  2. 因为返回的是super.dispatchTouchEvent(ev),所以事件ev将会被分发,但是MainActivity中没有onInterceptTouchEvent()方法,所以事件被传递到FatherView中的dispatchTouchEvent方法.打印FatherView-dispatchTouchEvent-->ACTION_DOWN
  3. 在FatherView中dispatchTouchEvent返回的是super.dispatchTouchEvent(ev),所有事件会被分发。FatherView中的onInterceptTouchEven()中的方法被执行。打印FatherView-onInterceptTouchEven-->ACTION_DOWN
  4. FatherView中的onInterceptTouchEven()返回的是super.onInterceptTouchEvent(ev)。在这里,(1)如果点击的是屏幕中的ChildView。事件将不会被拦截,会被传递到ChildView中的dispatchTouchEvent方法中。(2)如果点击的值FatherView则事件将会被拦截。FatherView中的onTouchEvent()方法将被执行。以(1)为例,将打印ChildView-dispatchTouchEvent-->ACTION_DOWN。
  5. ChildView中dispatchTouchEvent返回的是super.dispatchTouchEvent(ev),所有事件会被分发。打印ChildView-onInterceptTouchEvent-->ACTION_DOWN。
  6. 此时ChildView中onInterceptTouchEvent返回的是super.onInterceptTouchEvent(ev),,而且已经没有子控件了,所以事件将被拦截。打印ChildView-onTouchEvent-->ACTION_DOWN。
  7. 在childView中onTouchEvent()返回额是super.onTouchEvent(ev)。事件将不会被消耗,将以冒泡的方式传递到上层空间中的onTouchEvent(),此处上层空间中的onTouchEvent返回的都是super.onTouchEvent(ev)。所以讲一次打印 Father-onTouchEvent-->ACTION_DOWN。 MainActivty-onTouchEvent-->ACTION_DOWN。
  8. 之后的事件动作,将不再被MainActivity分发到子view,直接被MainActivty中的onTouchEvent处理消耗。打印MainActivity-dispatchTouchEvent-->ACTION_UP,MainActivty-onTouchEvent-->ACTION_UP

MainActivity-dispatchTouchEvent-->ACTION_DOWN

FatherView-dispatchTouchEvent-->ACTION_DOWN

FatherView-onInterceptTouchEven-->ACTION_DOWN

ChildView-dispatchTouchEvent-->ACTION_DOWN

ChildView-onInterceptTouchEvent-->ACTION_DOWN。

ChildView-onTouchEvent-->ACTION_DOWN

Father-onTouchEvent-->ACTION_DOWN。

MainActivty-onTouchEvent-->ACTION_DOWN

MainActivity-dispatchTouchEvent-->ACTION_UP,

MainActivty-onTouchEvent-->ACTION_UP

Android中触摸事件传递过程中最重要的是dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent()方法。这个是困扰初学者的问题之一,我开始也是。这里记录一下dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent()的处理过程,以供记忆。

dispatchTouchEvent是处理触摸事件分发,事件(多数情况)是从Activity的dispatchTouchEvent开始的。执行

super.dispatchTouchEvent(ev),事件向下分发。

onInterceptTouchEvent是ViewGroup提供的方法,默认返回false,返回true表示拦截。

onTouchEvent是View中提供的方法,ViewGroup也有这个方法,view中不提供onInterceptTouchEvent。view中默认返回false

View里,有两个回调函数 :

[java] view
plain
 copy

print?

  1. public boolean dispatchTouchEvent(MotionEvent ev);
  2. public boolean onTouchEvent(MotionEvent ev);

ViewGroup里,有三个回调函数 :

[java] view
plain
 copy

print?

  1. public boolean dispatchTouchEvent(MotionEvent ev);
  2. public boolean onInterceptTouchEvent(MotionEvent ev);
  3. public boolean onTouchEvent(MotionEvent ev);

在Activity里,有两个回调函数 :

[java] view
plain
 copy

print?

  1. public boolean dispatchTouchEvent(MotionEvent ev);
  2. public boolean onTouchEvent(MotionEvent ev);

Android中默认情况下事件传递是由最终的view的接收到,传递过程是从父布局到子布局,也就是从Activity到ViewGroup到View的过程,默认情况,ViewGroup起到的是透传作用。Android中事件传递过程(按箭头方向)如下图,图片来自[qiushuiqifei],谢谢[qiushuiqifei]整理。

触摸事件是一连串ACTION_DOWN,ACTION_MOVE..MOVE…MOVE、最后ACTION_UP,触摸事件还有ACTION_CANCEL事件。事件都是从ACTION_DOWN开始的,Activity的dispatchTouchEvent()首先接收到ACTION_DOWN,执行super.dispatchTouchEvent(ev),事件向下分发。

dispatchTouchEvent()返回true,后续事件(ACTION_MOVE、ACTION_UP)会再传递,如果返回false,dispatchTouchEvent()就接收不到ACTION_UP、ACTION_MOVE。

下面的几张图参考自[eoe]

图1.ACTION_DOWN都没被消费

图2-1.ACTION_DOWN被View消费了

图2-2.后续ACTION_MOVE和UP在不被拦截的情况下都会去找VIEW

图3.后续的被拦截了

图4ACTION_DOWN一开始就被拦截

android中的Touch事件都是从ACTION_DOWN开始的:

单手指操作:ACTION_DOWN---ACTION_MOVE----ACTION_UP

多手指操作:ACTION_DOWN---ACTION_POINTER_DOWN---ACTION_MOVE--ACTION_POINTER_UP---ACTION_UP.

时间: 2024-08-09 20:07:43

Android中的事件分发机制的相关文章

Android中的事件分发机制(下)——View的事件处理

综述 在上篇文章Android中的事件分发机制(上)--ViewGroup的事件分发中,对ViewGroup的事件分发进行了详细的分析.在文章的最后ViewGroup的dispatchTouchEvent方法调用dispatchTransformedTouchEvent方法成功将事件传递给ViewGroup的子View.并交由子View进行处理.那么现在就来分析一下子View接收到事件以后是如何处理的. View的事件处理 对于这里描述的View,它是ViewGroup的父类,并不包含任何的子元

Android中的事件分发机制——ViewGroup的事件分发

综述 Android中的事件分发机制也就是View与ViewGroup的对事件的分发与处理.在ViewGroup的内部包含了许多View,而ViewGroup继承自View,所以ViewGroup本身也是一个View.对于事件可以通过ViewGroup下发到它的子View并交由子View进行处理,而ViewGroup本身也能够对事件做出处理.下面就来详细分析一下ViewGroup对时间的分发处理. MotionEvent 当手指接触到屏幕以后,所产生的一系列的事件中,都是由以下三种事件类型组成.

Android中的事件分发机制总结

Android 的事件分发机制 一.View的事件分发总结: View的onTouchEvent和OnTouch区别  还是以自定义的TestButton为例. 我们可以通过重写onTouchEvent方法来处理诸如down move up的消息: public class TestButton extends Button { public TestButton(Context context) { super(context); // TODO Auto-generated construc

安卓中的事件分发机制源码解析

安卓中的事件分发机制主要涉及到两类控件,一类是容器类控件ViewGroup,如常用的布局控件,另一类是显示类控件,即该控件中不能用来容纳其它控件,它只能用来显示一些资源内容,如Button,ImageView等控件.暂且称前一类控件为ViewGroup类控件(尽管ViewGroup本身也是一个View),后者为View类控件. 安卓中的事件分发机制主要涉及到dispatchTouchEvent(MotionEvent ev).onInterceptTouchEvent(MotionEvent e

【Unity游戏开发】用C#和Lua实现Unity中的事件分发机制EventDispatcher

一.简介 最近马三换了一家大公司工作,公司制度规范了一些,因此平时的业余时间多了不少.但是人却懒了下来,最近这一个月都没怎么研究新技术,博客写得也是拖拖拉拉,周六周天就躺尸在家看帖子.看小说,要么就是吃鸡,唉!真是罪过罪过.希望能从这篇博客开始有些改善吧,尽量少玩耍,还是多学习吧~ 好了扯得有点远了,来说说我们今天博客的主题——“用C#和Lua实现Unity中的事件分发机制”,事件分发机制或者叫事件监听派发系统,在每个游戏框架中都是不可或缺的一个模块.我们可以用它来解耦,监听网络消息,或者做一些

Android的Touch事件分发机制简单探析

前言 Android中关于触摸事件的分发传递是一个很值得研究的东西.曾不见你引入了一个ListView的滑动功能,ListView就不听你手指的指唤来滚动了:也不知道为啥Button设置了onClick和onTouch,其中谁会先响应:或许你会问onTouch和onTouchEvent有什么区别,又该如何使用?这里一切的一切,只要你了解了事件分发机制,你会发现,解释这都不是事儿! 相关Touch事件的方法 1.public boolean dispatchTouchEvent(MotionEve

Android View的事件分发机制

准备了一阵子,一直想写一篇事件分发的文章总结一下.这个知识点实在是太重要了. 一个应用的布局是丰富的,有TextView,ImageView,Button等.这些子View的外层还有ViewGroup.如RelativeLayout.LinearLayout.作为一个开发人员,我们会思考.当点击一个button,Android系统是如何确定我点的就是button而不是TextView的?然后还正确的响应了button的点击事件. 内部经过了一系列什么过程呢? 先铺垫一些知识能更加清晰的理解事件分

Android view 的事件分发机制

1 事件的传递顺序是 Activity -> Window -> 顶层View touch 事件产生后,最先由 activity 的 dispatchTouchEvent 处理 /** * Called to process touch screen events. You can override this to * intercept all touch screen events before they are dispatched to the * window. Be sure to

Android View的事件分发机制探索

概述 Android事件传递机制也是Android系统中比较重要的一块,事件类型有很多种,这里主要讨论TouchEvent的事件在framework层的传递处理机制.因为对于App开发人员来说,理解framework层的事件传递机制,就差不多了. 带着问题来思考整个事件分发过程. 1.为什么要有事件分发过程? 当Android设备的屏幕,接收到触摸的动作时,屏幕驱动把压力信号(包括压力大小,压力位置等)传递给系统底层,然后操作系统经过一系列的处理,然后把触摸事件一层一层的向上传递,最终事件会被准