【转载】Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
分类:Android疑难解析 2013-07-05 08:26 35186人阅读 评论(98) 收藏 举报 Android 事件分发 dispatchTouchEvent onTouchEvent onInterceptTouchEven转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9153761
记得在前面的文章中,我带大家一起从源码的角度分析了Android中View的事件分发机制,相信阅读过的朋友对View的事件分发已经有比较深刻的理解了。
还未阅读过的朋友,请先参考Android事件分发机制完全解析,带你从源码的角度彻底理解(上)。
那么今天我们将继续上次未完成的话题,从源码的角度分析ViewGruop的事件分发。
首先我们来探讨一下,什么是ViewGroup?它和普通的View有什么区别?
顾名思义,ViewGroup就是一组View的集合,它包含很多的子View和子VewGroup,是Android中所有布局的父类或间接父类,像LinearLayout、RelativeLayout等都是继承自ViewGroup的。但ViewGroup实际上也是一个View,只不过比起View,它多了可以包含子View和定义布局参数的功能。ViewGroup继承结构示意图如下所示:
可以看到,我们平时项目里经常用到的各种布局,全都属于ViewGroup的子类。
简单介绍完了ViewGroup,我们现在通过一个Demo来演示一下Android中VewGroup的事件分发流程吧。
首先我们来自定义一个布局,命名为MyLayout,继承自LinearLayout,如下所示:
[java] view plain copy- publicclassMyLayoutextendsLinearLayout{
- publicMyLayout(Contextcontext,AttributeSetattrs){
- super(context,attrs);
- }
- }
然后,打开主布局文件activity_main.xml,在其中加入我们自定义的布局:
[html] view plain copy- <com.example.viewgrouptouchevent.MyLayoutxmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:id="@+id/my_layout"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
- <Button
- android:id="@+id/button1"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="Button1"/>
- <Button
- android:id="@+id/button2"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="Button2"/>
- </com.example.viewgrouptouchevent.MyLayout>
- myLayout.setOnTouchListener(newOnTouchListener(){
- @Override
- publicbooleanonTouch(Viewv,MotionEventevent){
- Log.d("TAG","myLayoutontouch");
- returnfalse;
- }
- });
- button1.setOnClickListener(newOnClickListener(){
- @Override
- publicvoidonClick(Viewv){
- Log.d("TAG","Youclickedbutton1");
- }
- });
- button2.setOnClickListener(newOnClickListener(){
- @Override
- publicvoidonClick(Viewv){
- Log.d("TAG","Youclickedbutton2");
- }
- });
分别点击一下Button1、Button2和空白区域,打印结果如下所示:
你会发现,当点击按钮的时候,MyLayout注册的onTouch方法并不会执行,只有点击空白区域的时候才会执行该方法。你可以先理解成Button的onClick方法将事件消费掉了,因此事件不会再继续向下传递。
那就说明Android中的touch事件是先传递到View,再传递到ViewGroup的?现在下结论还未免过早了,让我们再来做一个实验。
查阅文档可以看到,ViewGroup中有一个onInterceptTouchEvent方法,我们来看一下这个方法的源码:
[java] view plain copy- /**
- *Implementthismethodtointerceptalltouchscreenmotionevents.This
- *allowsyoutowatcheventsastheyaredispatchedtoyourchildren,and
- *takeownershipofthecurrentgestureatanypoint.
- *
- *<p>Usingthisfunctiontakessomecare,asithasafairlycomplicated
- *interactionwith{@linkView#onTouchEvent(MotionEvent)
- *View.onTouchEvent(MotionEvent)},andusingitrequiresimplementing
- *thatmethodaswellasthisoneinthecorrectway.Eventswillbe
- *receivedinthefollowingorder:
- *
- *<ol>
- *<li>Youwillreceivethedowneventhere.
- *<li>Thedowneventwillbehandledeitherbyachildofthisview
- *group,orgiventoyourownonTouchEvent()methodtohandle;thismeans
- *youshouldimplementonTouchEvent()toreturntrue,soyouwill
- *continuetoseetherestofthegesture(insteadoflookingfor
- *aparentviewtohandleit).Also,byreturningtruefrom
- *onTouchEvent(),youwillnotreceiveanyfollowing
- *eventsinonInterceptTouchEvent()andalltouchprocessingmust
- *happeninonTouchEvent()likenormal.
- *<li>Foraslongasyoureturnfalsefromthisfunction,eachfollowing
- *event(uptoandincludingthefinalup)willbedeliveredfirsthere
- *andthentothetarget'sonTouchEvent().
- *<li>Ifyoureturntruefromhere,youwillnotreceiveany
- *followingevents:thetargetviewwillreceivethesameeventbut
- *withtheaction{@linkMotionEvent#ACTION_CANCEL},andallfurther
- *eventswillbedeliveredtoyouronTouchEvent()methodandnolonger
- *appearhere.
- *</ol>
- *
- *@paramevThemotioneventbeingdispatcheddownthehierarchy.
- *@returnReturntruetostealmotioneventsfromthechildrenandhave
- *themdispatchedtothisViewGroupthroughonTouchEvent().
- *ThecurrenttargetwillreceiveanACTION_CANCELevent,andnofurther
- *messageswillbedeliveredhere.
- */
- publicbooleanonInterceptTouchEvent(MotionEventev){
- returnfalse;
- }
好吧,既然是布尔型的返回,那么只有两种可能,我们在MyLayout中重写这个方法,然后返回一个true试试,代码如下所示:
[java] view plain copy- publicclassMyLayoutextendsLinearLayout{
- publicMyLayout(Contextcontext,AttributeSetattrs){
- super(context,attrs);
- }
- @Override
- publicbooleanonInterceptTouchEvent(MotionEventev){
- returntrue;
- }
- }
你会发现,不管你点击哪里,永远都只会触发MyLayout的touch事件了,按钮的点击事件完全被屏蔽掉了!这是为什么呢?如果Android中的touch事件是先传递到View,再传递到ViewGroup的,那么MyLayout又怎么可能屏蔽掉Button的点击事件呢?
看来只有通过阅读源码,搞清楚Android中ViewGroup的事件分发机制,才能解决我们心中的疑惑了,不过这里我想先跟你透露一句,Android中touch事件的传递,绝对是先传递到ViewGroup,再传递到View的。记得在Android事件分发机制完全解析,带你从源码的角度彻底理解(上)中我有说明过,只要你触摸了任何控件,就一定会调用该控件的dispatchTouchEvent方法。这个说法没错,只不过还不完整而已。实际情况是,当你点击了某个控件,首先会去调用该控件所在布局的dispatchTouchEvent方法,然后在布局的dispatchTouchEvent方法中找到被点击的相应控件,再去调用该控件的dispatchTouchEvent方法。如果我们点击了MyLayout中的按钮,会先去调用MyLayout的dispatchTouchEvent方法,可是你会发现MyLayout中并没有这个方法。那就再到它的父类LinearLayout中找一找,发现也没有这个方法。那只好继续再找LinearLayout的父类ViewGroup,你终于在ViewGroup中看到了这个方法,按钮的dispatchTouchEvent方法就是在这里调用的。修改后的示意图如下所示:
那还等什么?快去看一看ViewGroup中的dispatchTouchEvent方法的源码吧!代码如下所示:
[java] view plain copy- publicbooleandispatchTouchEvent(MotionEventev){
- finalintaction=ev.getAction();
- finalfloatxf=ev.getX();
- finalfloatyf=ev.getY();
- finalfloatscrolledXFloat=xf+mScrollX;
- finalfloatscrolledYFloat=yf+mScrollY;
- finalRectframe=mTempRect;
- booleandisallowIntercept=(mGroupFlags&FLAG_DISALLOW_INTERCEPT)!=0;
- if(action==MotionEvent.ACTION_DOWN){
- if(mMotionTarget!=null){
- mMotionTarget=null;
- }
- if(disallowIntercept||!onInterceptTouchEvent(ev)){
- ev.setAction(MotionEvent.ACTION_DOWN);
- finalintscrolledXInt=(int)scrolledXFloat;
- finalintscrolledYInt=(int)scrolledYFloat;
- finalView[]children=mChildren;
- finalintcount=mChildrenCount;
- for(inti=count-1;i>=0;i--){
- finalViewchild=children[i];
- if((child.mViewFlags&VISIBILITY_MASK)==VISIBLE
- ||child.getAnimation()!=null){
- child.getHitRect(frame);
- if(frame.contains(scrolledXInt,scrolledYInt)){
- finalfloatxc=scrolledXFloat-child.mLeft;
- finalfloatyc=scrolledYFloat-child.mTop;
- ev.setLocation(xc,yc);
- child.mPrivateFlags&=~CANCEL_NEXT_UP_EVENT;
- if(child.dispatchTouchEvent(ev)){
- mMotionTarget=child;
- returntrue;
- }
- }
- }
- }
- }
- }
- booleanisUpOrCancel=(action==MotionEvent.ACTION_UP)||
- (action==MotionEvent.ACTION_CANCEL);
- if(isUpOrCancel){
- mGroupFlags&=~FLAG_DISALLOW_INTERCEPT;
- }
- finalViewtarget=mMotionTarget;
- if(target==null){
- ev.setLocation(xf,yf);
- if((mPrivateFlags&CANCEL_NEXT_UP_EVENT)!=0){
- ev.setAction(MotionEvent.ACTION_CANCEL);
- mPrivateFlags&=~CANCEL_NEXT_UP_EVENT;
- }
- returnsuper.dispatchTouchEvent(ev);
- }
- if(!disallowIntercept&&onInterceptTouchEvent(ev)){
- finalfloatxc=scrolledXFloat-(float)target.mLeft;
- finalfloatyc=scrolledYFloat-(float)target.mTop;
- mPrivateFlags&=~CANCEL_NEXT_UP_EVENT;
- ev.setAction(MotionEvent.ACTION_CANCEL);
- ev.setLocation(xc,yc);
- if(!target.dispatchTouchEvent(ev)){
- }
- mMotionTarget=null;
- returntrue;
- }
- if(isUpOrCancel){
- mMotionTarget=null;
- }
- finalfloatxc=scrolledXFloat-(float)target.mLeft;
- finalfloatyc=scrolledYFloat-(float)target.mTop;
- ev.setLocation(xc,yc);
- if((target.mPrivateFlags&CANCEL_NEXT_UP_EVENT)!=0){
- ev.setAction(MotionEvent.ACTION_CANCEL);
- target.mPrivateFlags&=~CANCEL_NEXT_UP_EVENT;
- mMotionTarget=null;
- }
- returntarget.dispatchTouchEvent(ev);
- }
这个时候你就可以思考一下了,由于我们刚刚在MyLayout中重写了onInterceptTouchEvent方法,让这个方法返回true,导致所有按钮的点击事件都被屏蔽了,那我们就完全有理由相信,按钮点击事件的处理就是在第13行条件判断的内部进行的!
那我们重点来看下条件判断的内部是怎么实现的。在第19行通过一个for循环,遍历了当前ViewGroup下的所有子View,然后在第24行判断当前遍历的View是不是正在点击的View,如果是的话就会进入到该条件判断的内部,然后在第29行调用了该View的dispatchTouchEvent,之后的流程就和Android事件分发机制完全解析,带你从源码的角度彻底理解(上)中讲解的是一样的了。我们也因此证实了,按钮点击事件的处理确实就是在这里进行的。
然后需要注意一下,调用子View的dispatchTouchEvent后是有返回值的。我们已经知道,如果一个控件是可点击的,那么点击该控件时,dispatchTouchEvent的返回值必定是true。因此会导致第29行的条件判断成立,于是在第31行给ViewGroup的dispatchTouchEvent方法直接返回了true。这样就导致后面的代码无法执行到了,也是印证了我们前面的Demo打印的结果,如果按钮的点击事件得到执行,就会把MyLayout的touch事件拦截掉。
那如果我们点击的不是按钮,而是空白区域呢?这种情况就一定不会在第31行返回true了,而是会继续执行后面的代码。那我们继续往后看,在第44行,如果target等于null,就会进入到该条件判断内部,这里一般情况下target都会是null,因此会在第50行调用super.dispatchTouchEvent(ev)。这句代码会调用到哪里呢?当然是View中的dispatchTouchEvent方法了,因为ViewGroup的父类就是View。之后的处理逻辑又和前面所说的是一样的了,也因此MyLayout中注册的onTouch方法会得到执行。之后的代码在一般情况下是走不到的了,我们也就不再继续往下分析。
再看一下整个ViewGroup事件分发过程的流程图吧,相信可以帮助大家更好地去理解:
现在整个ViewGroup的事件分发流程的分析也就到此结束了,我们最后再来简单梳理一下吧。
1. Android事件分发是先传递到ViewGroup,再由ViewGroup传递到View的。
2. 在ViewGroup中可以通过onInterceptTouchEvent方法对事件传递进行拦截,onInterceptTouchEvent方法返回true代表不允许事件继续向子View传递,返回false代表不对事件进行拦截,默认返回false。
3. 子View中如果将传递的事件消费掉,ViewGroup中将无法接收到任何事件。
好了,Android事件分发机制完全解析到此全部结束,结合上下两篇,相信大家对事件分发的理解已经非常深刻了。
更多相关文章
- Android SDK Manager 更新方法
- android 源码下载 ARM/x86架构
- android 实现时钟的简单方法
- Android Studio SVN 使用方法
- Android Virtual Device Manager 创建虚拟机出现SDK Manager NUL
- Android Input输入系统之一:KeyEvent事件监听
- Android中Activity共享变量的另一方法:Application context