码迷,mamicode.com
首页 > 移动开发 > 详细

Android中的事件分发机制

时间:2016-05-27 13:05:18      阅读:309      评论:0      收藏:0      [点我收藏+]

标签:

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.   
  6.     if (onFilterTouchEventForSecurity(event)) {  
  7.         //noinspection SimplifiableIfStatement  
  8.         ListenerInfo li = mListenerInfo;  
  9.         if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED  
  10.                 && li.mOnTouchListener.onTouch(this, event)) {  
  11.             return true;  
  12.         }  
  13.   
  14.         if (onTouchEvent(event)) {  
  15.             return true;  
  16.         }  
  17.     }  
  18.   
  19.     if (mInputEventConsistencyVerifier != null) {  
  20.         mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);  
  21.     }  
  22.     return false;  
  23. }  
  

整个程序中有最关键的两处判断(第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.   
  3.         //如果是按下状态就调用onUserInteraction()方法,onUserInteraction()方法  
  4.         //是个空的方法, 我们直接跳过这里看下面的实现  
  5.         if (ev.getAction() == MotionEvent.ACTION_DOWN) {  
  6.             onUserInteraction();  
  7.         }  
  8.           
  9.         if (getWindow().superDispatchTouchEvent(ev)) {  
  10.             return true;  
  11.         }  
  12.           
  13.         //getWindow().superDispatchTouchEvent(ev)返回false,这个事件就交给Activity  
  14.         //来处理, Activity的onTouchEvent()方法直接返回了false  
  15.         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) 

onInterceptTouchEventViewGroup提供的方法,默认返回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),事件向下分发。

    onInterceptTouchEventViewGroup提供的方法,默认返回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.



Android中的事件分发机制

标签:

原文地址:http://blog.csdn.net/xuzhuaaron1/article/details/51485714

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!