标签:
最近的一个项目中涉及,布局为一个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中,所有的操作类型事件都由如下三个部分作为基础:
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 |
为了证明这一逻辑,我们对代码一层层地分析,首先看下View中的dispatchTouchEvent方法:
View.Java
整个程序中有最关键的两处判断(第9行和14行),在第9行的if条件中,如果li.mOnTouchListener != null,(mViewFlags & ENABLED_MASK) == ENABLED 和 mOnTouchListener.onTouch(this, event) == true这三个条件同时满足时,就直接返回true。
第一个条件:mOnTouchListener在哪里赋值呢?CRTL+F搜索,发现了View.java中的如下方法:
View.java
太眼熟了,这不正是我们平时注册onTouch事件所使用的函数么,这下明白了,这要注册了onTouch事件,mOnTouchListener就一定不为空。好了,继续往下看。
第二个条件:(mViewFlags & ENABLED_MASK) == ENABLED 判断当前的View是否为ENABLED,一般控件默认都是ENABLED,因此这个条件也成立。
第三个条件:mOnTouchListener.onTouch(this, event) == true,即我们注册的onTouch方法中,如果返回true,就会使三个条件都成立,这样,就不会继续接下来的判断了。
在第14行的If判断中,只是简单的判断了onTouchEvent方法的返回是否为true。
上述短短的几行代码解开了我的第三个疑问,下面来总结一下:
关于Android中的事件机制,用到的地方还是很多的,并且这个知识点还真有点复杂。
在写这篇文章前,网上看了不少博文,有的写的感觉挺不错的。只是当时感觉好像理解了,事后又很容易忘。现在自己也系统整理下吧。
Android中的事件在表现形式上有很多,如onTouch、onClick和onLongClick等,在具体微观上的表现形势有action_down、action_move和action_up等。
无论哪种事件表现类型,首先都是基于事件的传递模型。其实Android中的事件传递有点类似于JS中事件传递模型。都是基于先捕获然后冒泡的形式。
在捕获阶段,事件先由外部的View接收,然后传递给其内层的View,依次传递到更够接收此事件的最小View单元,完成事件捕获过程;
在冒泡阶段,事件则从事件源的最小View单元开始,依次向外冒泡,将事件对层传递。
事件的捕获和冒泡是整个事件的传递流程,但是在实际的传递过程中,Android中则表现的相对复杂。
主要表现在可以控制每层事件是否继续传递(由事件分发和事件拦截协同进行),以及事件的具体消费(由事件消响应进行,但需要注意的是,事件分发自身也具有事件消费能力)。
也就是本文提及的事件分发、拦截和响应。
Android中不同的控件所具有的事件分发、拦截和响应稍有不同,主要表现在Activity本身不具有事件拦截,不是ViewGroup的最小view单元不具有事件拦截(因为它没有自己的子View)
事件分发: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里面:
从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),则事件将不会被拦截,会被分发到子控件中)由于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()处理。
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里,有两个回调函数 :
ViewGroup里,有三个回调函数 :
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.
标签:
原文地址:http://blog.csdn.net/xuzhuaaron1/article/details/51485714