View工作原理(一)事件传递原理详解http://blog.csdn.net/ff20081528/article/details/17353869
2015-12-10 20:06
501 查看
一、准备知识
1、视图坐标与布局坐标的区别如下图所示:
二、例子说明事件分发过程
[java]view
plaincopy
@Override
publicbooleandispatchTouchEvent(MotionEventev){
if(!onFilterTouchEventForSecurity(ev)){
returnfalse;
}
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){
//thisisweird,wegotapendown,butwethoughtitwas
//alreadydown!
//XXX:WeshouldprobablysendanACTION_UPtothecurrent
//target.
mMotionTarget=null;
}
//Ifwe'redisallowinginterceptorifwe'reallowingandwedidn't
//intercept
if(disallowIntercept||!onInterceptTouchEvent(ev)){
//resetthisevent'saction(justtoprotectourselves)
ev.setAction(MotionEvent.ACTION_DOWN);
//Weknowwewanttodispatchtheeventdown,findachild
//whocanhandleit,startwiththefront-mostchild.
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)){
//offsettheeventtotheview'scoordinatesystem
finalfloatxc=scrolledXFloat-child.mLeft;
finalfloatyc=scrolledYFloat-child.mTop;
ev.setLocation(xc,yc);
child.mPrivateFlags&=~CANCEL_NEXT_UP_EVENT;
if(child.dispatchTouchEvent(ev)){
//Eventhandled,wehaveatargetnow.
mMotionTarget=child;
returntrue;
}
//Theeventdidn'tgethandled,trythenextview.
//Don'tresettheevent'slocation,it'snot
//necessaryhere.
}
}
}
}
}
booleanisUpOrCancel=(action==MotionEvent.ACTION_UP)||
(action==MotionEvent.ACTION_CANCEL);
if(isUpOrCancel){
//Note,we'vealreadycopiedthepreviousstatetoourlocal
//variable,sothistakeseffectonthenextevent
mGroupFlags&=~FLAG_DISALLOW_INTERCEPT;
}
//Theeventwasn'tanACTION_DOWN,dispatchittoourtargetif
//wehaveone.
finalViewtarget=mMotionTarget;
if(target==null){
//Wedon'thaveatarget,thismeanswe'rehandlingthe
//eventasaregularview.
ev.setLocation(xf,yf);
if((mPrivateFlags&CANCEL_NEXT_UP_EVENT)!=0){
ev.setAction(MotionEvent.ACTION_CANCEL);
mPrivateFlags&=~CANCEL_NEXT_UP_EVENT;
}
returnsuper.dispatchTouchEvent(ev);
}
//ifhaveatarget,seeifwe'reallowedtoandwanttointerceptits
//events
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)){
//targetdidn'thandleACTION_CANCEL.notmuchwecando
//buttheyshouldhave.
}
//clearthetarget
mMotionTarget=null;
//Don'tdispatchthiseventtoourownview,becausewealready
//sawitwhenintercepting;wejustwanttogivethefollowing
//eventtothenormalonTouchEvent().
returntrue;
}
if(isUpOrCancel){
mMotionTarget=null;
}
//finallyoffsettheeventtothetarget'scoordinatesystemand
//dispatchtheevent.
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);
}
代码说明:
1、(代码2-4行)处理窗口处于模糊显示状态下的消息。所谓的模糊显示是指,应用程序可以设置当前窗口为模糊状态,此时窗口内部的所有视图将显示为模糊效果。这样的目的是为了隐藏窗口中的内容,对于其中的各个视图而言,可以设置改视图的FILTER_TOUCHERS_WHEN_OBSCURED标识,如存在该标识,则意味着用户希望不要处理该消息。
2、(代码6-11行)将ViewGroup的布局坐标转换成视图坐标。
3、(代码16-58行)处理ACTION_DOWN消息,其作用是判断该视图坐标落到了哪个子视图中。首先判断该ViewGroup本身是否被禁止获取TOUCH消息,如果没有禁止,并且回调函数onInterceptTouchEvent中没有消耗该消息,则开始寻找子视图。调用child.getHitRect(frame)方法获取该子视图在父视图中的布局坐标,即ViewGroup将child放在什么位置,这个位置相对于该child来讲是布局坐标,而对于该ViewGroup来讲却是视图坐标,参数frame是执行完毕后的位置输出矩形。得到位置后,就可以调用frame.contain()方法判断该消息位置是否被包含到了该child中,如果包含,并且该child也是一个ViewGroup,则准备递归调用该child的dispatchTouchEvent(),在调用之前,首先需要把坐标重新转换到child的坐标系中。接下来判断该child是否是ViewGroup类。如果是,则诋毁调用ViewGroup的dispatchTouchEvent(),重新从第一步开始执行;如果child不是ViewGroup,而是一个View,则意味着诋毁调用的结束。
5、(代码70-82)判断target变量是否为空。空代表了所有子窗口都没有消耗该消息,所以该ViewGroup本事需要处理该消息。然后还原消息的原始位置,将视图坐标重新转换成布局坐标,接着调用super.dispatchTouchEvent(ev),即View类的中的dispatchTouchEvent()方法(该方法会判断是否进行调用touchEvent()方法来处理,到后面的View类的源码中再介绍)。
6、(代码84-121)处理target存在,并且变量disallowIntercept为false,即允许截获,在默认情况下ViewGroup都是允许截获消息的,只有当该ViewGroup的子视图调用父视图的requestDisallowdInterceptTouchEvent()方法时,方可禁止父视图再次截获消息,但每次ACTION_UP消息或者ACTION_CANCEL消息之后,该ViewGroup又会重新截获消息。ViewGroup本身不允许截获消息或者允许截获但是却没有消耗消息,于是调用target.dispatchTouchEvent(ev)方法把该消息继续交给目标视图处理,在调用该方法前需要检查target中是否申明过要取消随后的消息,即mPrivateFlags中包含
CANCEL_NEXT_UP_EVENT,如果是,则把消息action值修改为ACTION_CANCEL,将mMotionTarget变为空,因为target不想处理接下来的消息了,那么就可以认为没有target了。
四、View中处理消息的详细过程
dispatchTouchEvent()方法的源码
[java]view
plaincopy
/**
*Passthetouchscreenmotioneventdowntothetargetview,orthis
*viewifitisthetarget.
*
*@parameventThemotioneventtobedispatched.
*@returnTrueiftheeventwashandledbytheview,falseotherwise.
*/
publicbooleandispatchTouchEvent(MotionEventevent){
if(!onFilterTouchEventForSecurity(event)){
returnfalse;
}
if(mOnTouchListener!=null&&(mViewFlags&ENABLED_MASK)==ENABLED&&
mOnTouchListener.onTouch(this,event)){
returntrue;
}
returnonTouchEvent(event);
}
处理窗口处于模糊显示状态下的消息。然后回调视图监听着的onTouch()方法,如果监听者消耗了该消息,则直接返回。如果没有则调用View的onTouchEvent()方法。
onTouchEvent()方法源码
[java]view
plaincopy
/**
*Implementthismethodtohandletouchscreenmotionevents.
*
*@parameventThemotionevent.
*@returnTrueiftheeventwashandled,falseotherwise.
*/
publicbooleanonTouchEvent(MotionEventevent){
finalintviewFlags=mViewFlags;
if((viewFlags&ENABLED_MASK)==DISABLED){
//Adisabledviewthatisclickablestillconsumesthetouch
//events,itjustdoesn'trespondtothem.
return(((viewFlags&CLICKABLE)==CLICKABLE||
(viewFlags&LONG_CLICKABLE)==LONG_CLICKABLE));
}
if(mTouchDelegate!=null){
if(mTouchDelegate.onTouchEvent(event)){
returntrue;
}
}
if(((viewFlags&CLICKABLE)==CLICKABLE||
(viewFlags&LONG_CLICKABLE)==LONG_CLICKABLE)){
switch(event.getAction()){
caseMotionEvent.ACTION_UP:
booleanprepressed=(mPrivateFlags&PREPRESSED)!=0;
if((mPrivateFlags&PRESSED)!=0||prepressed){
//takefocusifwedon'thaveitalreadyandweshouldin
//touchmode.
booleanfocusTaken=false;
if(isFocusable()&&isFocusableInTouchMode()&&!isFocused()){
focusTaken=requestFocus();
}
if(!mHasPerformedLongPress){
//Thisisatap,soremovethelongpresscheck
removeLongPressCallback();
//Onlyperformtakeclickactionsifwewereinthepressedstate
if(!focusTaken){
//UseaRunnableandpostthisratherthancalling
//performClickdirectly.Thisletsothervisualstate
//oftheviewupdatebeforeclickactionsstart.
if(mPerformClick==null){
mPerformClick=newPerformClick();
}
if(!post(mPerformClick)){
performClick();
}
}
}
if(mUnsetPressedState==null){
mUnsetPressedState=newUnsetPressedState();
}
if(prepressed){
mPrivateFlags|=PRESSED;
refreshDrawableState();
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
}elseif(!post(mUnsetPressedState)){
//Ifthepostfailed,unpressrightnow
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
caseMotionEvent.ACTION_DOWN:
if(mPendingCheckForTap==null){
mPendingCheckForTap=newCheckForTap();
}
mPrivateFlags|=PREPRESSED;
mHasPerformedLongPress=false;
postDelayed(mPendingCheckForTap,ViewConfiguration.getTapTimeout());
break;
caseMotionEvent.ACTION_CANCEL:
mPrivateFlags&=~PRESSED;
refreshDrawableState();
removeTapCallback();
break;
caseMotionEvent.ACTION_MOVE:
finalintx=(int)event.getX();
finalinty=(int)event.getY();
//Belenientaboutmovingoutsideofbuttons
intslop=mTouchSlop;
if((x<0-slop)||(x>=getWidth()+slop)||
(y<0-slop)||(y>=getHeight()+slop)){
//Outsidebutton
removeTapCallback();
if((mPrivateFlags&PRESSED)!=0){
//Removeanyfuturelongpress/tapchecks
removeLongPressCallback();
//Needtoswitchfrompressedtonotpressed
mPrivateFlags&=~PRESSED;
refreshDrawableState();
}
}
break;
}
returntrue;
}
returnfalse;
}
<p></p>
1、代码10-15)处理该视图是否为disable状态,如果是,什么都不处理,返回true,即消耗该消息。
2、(代码17-21)消息代理处理消息。所谓的消息代理是指,可以给某个View指定一个消息处理代理,当View收到消息时,首先将该消息派发给其代理进行处理。如果代理内部消耗了该消息,则View不需要在进行任何处理;如果代理没有处理,则view继续处理。在这里不用多管,不会影响事件处理的逻辑和结果。
3、(代码22-102)判断该视图是否可以点击,如果不可以点击,则直接返回false,即不处理该消息。否则,真正开始执行触摸消息的默认处理逻辑,该逻辑分别处理了ACTION_DOWN、ACTION_MOVE、ACTION_UP消息.
(26-70)处理ACTION_UP消息,判断该UP消息是否发生在前面所讲的哪一个监测时间段中,并据此进行不同的处理。
(71-78)在ACTION_DOWN消息中,给mPrivateFlags变量添加PRESSED标识,并将变量mHasPerformLongPress设置为false,然后启动tap监测,即发送一个异步延迟消息。
对于触摸消息而言,消息本身没有repeat的属性,一次触摸只有一个ACTION_DOWN消息,按下来就是连续的ACTION_MOVE消息,并最终以处理ACTION_CANCEL消息.
(80-85)ACTION_CANCLE消息处理,这里只需清除PRESSED标识,刷新视图状态,然后再关闭tap监测即可。
(86-105)对ACTION_MOVE消息进行处理。具体逻辑包括,判断是否移动到了视图区域以外,如果是,则删除tap或者longPress的监测,并清除mPrivateFlags中的PRESSED标识,然后调用refreshDrawableState()刷新视图状态,这将导致对背景状态进行重绘。
五、例子程序
通过代码可能完全理解事件的分发过程有点难,下面通过一个例子程序来巩固下事件分发的过程。
例子程序的UI如下:
布局如下:
[java]view
plaincopy
<org.sunday.main.MyLinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<org.sunday.main.MyLinearLayout
android:id="@+id/ll2"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="@drawable/ll_selector"
android:orientation="vertical">
</org.sunday.main.MyLinearLayout>
<org.sunday.main.MyLinearLayout
android:id="@+id/ll1"
android:layout_width="match_parent"
android:layout_marginTop="20dp"
android:layout_height="100dp"
android:background="@drawable/ll_selector"
android:orientation="vertical">
<org.sunday.main.MyLinearLayout
android:layout_width="80dp"
android:layout_height="50dp"
android:layout_marginTop="20dp"
android:clickable="true"
android:background="@drawable/ll_selector2"
android:orientation="vertical">
</org.sunday.main.MyLinearLayout>
</org.sunday.main.MyLinearLayout>
<org.sunday.main.MyLinearLayout
android:id="@+id/ll3"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginTop="20dp"
android:background="@drawable/ll_selector"
android:orientation="vertical">
<org.sunday.main.MyButton
android:layout_width="wrap_content"
android:layout_height="60dp"
android:layout_margin="20dp"
android:background="@drawable/btn_selector"
android:text="button1"/>
</org.sunday.main.MyLinearLayout>
<org.sunday.main.MyLinearLayout
android:id="@+id/ll4"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginTop="20dp"
android:background="@drawable/ll_selector"
android:orientation="vertical">
<org.sunday.main.MyButtonWithOutEvent
android:layout_width="wrap_content"
android:layout_height="60dp"
android:layout_margin="20dp"
android:background="@drawable/btn_selector"
android:text="button2"/>
</org.sunday.main.MyLinearLayout>
</org.sunday.main.MyLinearLayout>
MyLinearLayout.java
[java]view
plaincopy
packageorg.sunday.main;
importandroid.content.Context;
importandroid.util.AttributeSet;
importandroid.util.Log;
importandroid.view.MotionEvent;
importandroid.widget.LinearLayout;
publicclassMyLinearLayoutextendsLinearLayout{
privatefinalstaticStringTAG="MyLinearLayout";
publicMyLinearLayout(Contextcontext){
super(context);
}
publicMyLinearLayout(Contextcontext,AttributeSetattrs){
super(context,attrs);
}
@Override
publicbooleandispatchTouchEvent(MotionEventev){
Log.e(TAG,"dispatchTouchEvent()----------------"+this.toString());
booleanisTrue=super.dispatchTouchEvent(ev);
Log.e(TAG,"dispatchTouchEvent()isTrue="+isTrue+"---------------;"+this.toString());
returnisTrue;
}
@Override
publicbooleanonTouchEvent(MotionEventevent){
Log.e(TAG,"onTouchEvent()----------------"+this.toString());
booleanisTrue=super.onTouchEvent(event);
Log.e(TAG,"onTouchEvent()isTrue="+isTrue+"-----------------;"+this.toString());
returnisTrue;
}
}
MyButton.java
[java]view
plaincopy
packageorg.sunday.main;
importandroid.content.Context;
importandroid.util.AttributeSet;
importandroid.util.Log;
importandroid.view.MotionEvent;
importandroid.widget.Button;
publicclassMyButtonextendsButton{
privatefinalstaticStringTAG="MyButton";
publicMyButton(Contextcontext){
super(context);
}
publicMyButton(Contextcontext,AttributeSetattrs){
super(context,attrs);
}
@Override
publicbooleandispatchTouchEvent(MotionEventevent){
Log.e(TAG,"dispatchTouchEvent()----------------");
booleanisTrue=super.dispatchTouchEvent(event);
Log.e(TAG,"dispatchTouchEventisTrue="+isTrue);
returnisTrue;
}
@Override
publicbooleanonTouchEvent(MotionEventevent){
Log.e(TAG,"onTouchEvent()----------------");
booleanisTrue=super.onTouchEvent(event);
Log.e(TAG,"onTouchEventisTrue="+isTrue);
returnisTrue;
}
}
MyButtonWithOutEvent.java
[java]view
plaincopy
packageorg.sunday.main;
importandroid.content.Context;
importandroid.util.AttributeSet;
importandroid.util.Log;
importandroid.view.MotionEvent;
importandroid.widget.Button;
publicclassMyButtonWithOutEventextendsButton{
privatefinalstaticStringTAG="MyButtonWithOutEvent";
publicMyButtonWithOutEvent(Contextcontext){
super(context);
}
publicMyButtonWithOutEvent(Contextcontext,AttributeSetattrs){
super(context,attrs);
}
@Override
publicbooleandispatchTouchEvent(MotionEventevent){
Log.e(TAG,"dispatchTouchEvent()----------------"+this.toString());
//booleanisTrue=super.dispatchTouchEvent(event);
//Log.e(TAG,"isTrue="+isTrue);
returnfalse;
}
@Override
publicbooleanonTouchEvent(MotionEventevent){
Log.e(TAG,"onTouchEvent()----------------");
booleanisTrue=super.onTouchEvent(event);
Log.e(TAG,"onTouchEventisTrue="+isTrue);
returnisTrue;
}
}
点击例子一,后台打印的日志如下:
解释:例子一的布局中只有两个ViewGroup子类对象,当点击例子一区域,根ViewGroup首先分发touch事件,因为它有child,所以接着会将事件传递到它的孩子ViewGroup。孩子ViewGroup发现自己没有child了,所以它就得自己处理这个touch事件。所以会调用OnTouchEvent()方法来处理这个事件。处理完之后会返回一个true,一直返回给根ViewGroup。
点击例子二的绿色区域,后台打印日志如下:
解释:例子二和例子一差不多,只不过多了一个ViewGroup而已。
点击例子三得黄色区域,后台打印日志如下:
解释:例子三相比于例子二只是将最上层的ViewGroup换成了View,原理一样。
点击例子四的黄色区域,打印日志如下:
解释:在这个程序中,对于MyLinearLayout和MyButton的dispachTouchEvent()和onTouchEvent()发放的逻辑并没有进行任何的修改。而对于MyButtonWithOutEvent,在dispatchTouchEvent()方法中,直接返回的false(即不进行事件的任何处理),那么事件传递到这里时,它将会把事件返回给调用它的父ViewGroup进行处理,所以这里我们看到的是MyLinearLayout调用了onTouchEvent()。
点击例子三得button和点击例子四的button效果图如下:
例子三中是button响应了这个touch事件,而在例子四中则是LinearLayout响应了这个touch事件。
demo:点击打开链接
================================
其他参考链接
http://www.tuicool.com/articles/ZVbUnme
http://my.oschina.net/u/1538627/blog/217609?p={{totalPage}}
/article/5419333.html
/article/5050759.html
1、视图坐标与布局坐标的区别如下图所示:
上图是一个坐标系,这个坐标系是无边无际的。这个无边无际的坐标系即视图坐标。手机屏幕可视范围内的坐标即手机屏幕的布局坐标(坐标原点是屏幕的左上方的(0,0)位置)即A点。屏幕里面的子视图里面可视范围内的坐标即子视图的布局坐标(坐标原点是子视图的左上方的(0,0)位置)即B点。
2、android中布局关系
二、例子说明事件分发过程
这里我写了一个布局文件,展示效果如上图。当我点击View1,事件的分发过程是这样的:
1、ViewGroup3的dispatchTouchEvent()方法会被调用。
2、ViewGroup3调用ViewGroup2的dispatchTouchEvent()方法。
3、ViewGroup2调用ViewGroup1的dispatchTouchEvent()方法。
4、ViewGroup1会调用View1的dispatchTouchEvent()方法。
5、View1的dispatchTouchEvent()方法调用自己的onTouchEvent()方法。在onTouchEvent方法中处理点击事件。处理完了后会返回一个true给调用它的dispatchTouchEvent()方法。
6、ViewGroup1的dispatchTouchEvent()方法会返回一个true值给ViewGroup2的dispatchTouchEvent()方法。这样一直将则个true值返回到ViewGroup3的dispatchTouchEvent()方法。ViewGroup3在将这个值返回给调用它的方法。这样一个事件分发过程结束。
转载请说明出处:/article/7965394.html
三、ViewGroup中处理消息的详细过程
通过上面的列子对事件分发的过程有个大概的了解之后,我们通过ViewGroup中的dispatchTouchEvent()方法源码和View中的dispatchTouchEvent()方法及touchEvent方法源码来详细了解android是怎样处理这个过程的。
ViewGroup中的dispatchTouchEvent()方法源码如下:
[java]view
plain
@Override
publicbooleandispatchTouchEvent(MotionEventev){
if(!onFilterTouchEventForSecurity(ev)){
returnfalse;
}
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){
//thisisweird,wegotapendown,butwethoughtitwas
//alreadydown!
//XXX:WeshouldprobablysendanACTION_UPtothecurrent
//target.
mMotionTarget=null;
}
//Ifwe'redisallowinginterceptorifwe'reallowingandwedidn't
//intercept
if(disallowIntercept||!onInterceptTouchEvent(ev)){
//resetthisevent'saction(justtoprotectourselves)
ev.setAction(MotionEvent.ACTION_DOWN);
//Weknowwewanttodispatchtheeventdown,findachild
//whocanhandleit,startwiththefront-mostchild.
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)){
//offsettheeventtotheview'scoordinatesystem
finalfloatxc=scrolledXFloat-child.mLeft;
finalfloatyc=scrolledYFloat-child.mTop;
ev.setLocation(xc,yc);
child.mPrivateFlags&=~CANCEL_NEXT_UP_EVENT;
if(child.dispatchTouchEvent(ev)){
//Eventhandled,wehaveatargetnow.
mMotionTarget=child;
returntrue;
}
//Theeventdidn'tgethandled,trythenextview.
//Don'tresettheevent'slocation,it'snot
//necessaryhere.
}
}
}
}
}
booleanisUpOrCancel=(action==MotionEvent.ACTION_UP)||
(action==MotionEvent.ACTION_CANCEL);
if(isUpOrCancel){
//Note,we'vealreadycopiedthepreviousstatetoourlocal
//variable,sothistakeseffectonthenextevent
mGroupFlags&=~FLAG_DISALLOW_INTERCEPT;
}
//Theeventwasn'tanACTION_DOWN,dispatchittoourtargetif
//wehaveone.
finalViewtarget=mMotionTarget;
if(target==null){
//Wedon'thaveatarget,thismeanswe'rehandlingthe
//eventasaregularview.
ev.setLocation(xf,yf);
if((mPrivateFlags&CANCEL_NEXT_UP_EVENT)!=0){
ev.setAction(MotionEvent.ACTION_CANCEL);
mPrivateFlags&=~CANCEL_NEXT_UP_EVENT;
}
returnsuper.dispatchTouchEvent(ev);
}
//ifhaveatarget,seeifwe'reallowedtoandwanttointerceptits
//events
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)){
//targetdidn'thandleACTION_CANCEL.notmuchwecando
//buttheyshouldhave.
}
//clearthetarget
mMotionTarget=null;
//Don'tdispatchthiseventtoourownview,becausewealready
//sawitwhenintercepting;wejustwanttogivethefollowing
//eventtothenormalonTouchEvent().
returntrue;
}
if(isUpOrCancel){
mMotionTarget=null;
}
//finallyoffsettheeventtothetarget'scoordinatesystemand
//dispatchtheevent.
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);
}
代码说明:
1、(代码2-4行)处理窗口处于模糊显示状态下的消息。所谓的模糊显示是指,应用程序可以设置当前窗口为模糊状态,此时窗口内部的所有视图将显示为模糊效果。这样的目的是为了隐藏窗口中的内容,对于其中的各个视图而言,可以设置改视图的FILTER_TOUCHERS_WHEN_OBSCURED标识,如存在该标识,则意味着用户希望不要处理该消息。
2、(代码6-11行)将ViewGroup的布局坐标转换成视图坐标。
3、(代码16-58行)处理ACTION_DOWN消息,其作用是判断该视图坐标落到了哪个子视图中。首先判断该ViewGroup本身是否被禁止获取TOUCH消息,如果没有禁止,并且回调函数onInterceptTouchEvent中没有消耗该消息,则开始寻找子视图。调用child.getHitRect(frame)方法获取该子视图在父视图中的布局坐标,即ViewGroup将child放在什么位置,这个位置相对于该child来讲是布局坐标,而对于该ViewGroup来讲却是视图坐标,参数frame是执行完毕后的位置输出矩形。得到位置后,就可以调用frame.contain()方法判断该消息位置是否被包含到了该child中,如果包含,并且该child也是一个ViewGroup,则准备递归调用该child的dispatchTouchEvent(),在调用之前,首先需要把坐标重新转换到child的坐标系中。接下来判断该child是否是ViewGroup类。如果是,则诋毁调用ViewGroup的dispatchTouchEvent(),重新从第一步开始执行;如果child不是ViewGroup,而是一个View,则意味着诋毁调用的结束。
4、(代码61-68行)如果是ACTION_UP或者是ACTION_CANCEL消息,则清除mGroupFlags中的FLAG_DISALLOW_INTERCEPT标识,即允许该ViewGroup截获消息。也就是说,常见的情况就是当用户释放手指,下一次按下时,该ViewGroup本身可以重新截获消息,而在按下还没有释放期间,ViewGroup本身是不允许截获消息的。
5、(代码70-82)判断target变量是否为空。空代表了所有子窗口都没有消耗该消息,所以该ViewGroup本事需要处理该消息。然后还原消息的原始位置,将视图坐标重新转换成布局坐标,接着调用super.dispatchTouchEvent(ev),即View类的中的dispatchTouchEvent()方法(该方法会判断是否进行调用touchEvent()方法来处理,到后面的View类的源码中再介绍)。
6、(代码84-121)处理target存在,并且变量disallowIntercept为false,即允许截获,在默认情况下ViewGroup都是允许截获消息的,只有当该ViewGroup的子视图调用父视图的requestDisallowdInterceptTouchEvent()方法时,方可禁止父视图再次截获消息,但每次ACTION_UP消息或者ACTION_CANCEL消息之后,该ViewGroup又会重新截获消息。ViewGroup本身不允许截获消息或者允许截获但是却没有消耗消息,于是调用target.dispatchTouchEvent(ev)方法把该消息继续交给目标视图处理,在调用该方法前需要检查target中是否申明过要取消随后的消息,即mPrivateFlags中包含
CANCEL_NEXT_UP_EVENT,如果是,则把消息action值修改为ACTION_CANCEL,将mMotionTarget变为空,因为target不想处理接下来的消息了,那么就可以认为没有target了。
四、View中处理消息的详细过程
dispatchTouchEvent()方法的源码
[java]view
plain
/**
*Passthetouchscreenmotioneventdowntothetargetview,orthis
*viewifitisthetarget.
*
*@parameventThemotioneventtobedispatched.
*@returnTrueiftheeventwashandledbytheview,falseotherwise.
*/
publicbooleandispatchTouchEvent(MotionEventevent){
if(!onFilterTouchEventForSecurity(event)){
returnfalse;
}
if(mOnTouchListener!=null&&(mViewFlags&ENABLED_MASK)==ENABLED&&
mOnTouchListener.onTouch(this,event)){
returntrue;
}
returnonTouchEvent(event);
}
处理窗口处于模糊显示状态下的消息。然后回调视图监听着的onTouch()方法,如果监听者消耗了该消息,则直接返回。如果没有则调用View的onTouchEvent()方法。
onTouchEvent()方法源码
[java]view
plain
/**
*Implementthismethodtohandletouchscreenmotionevents.
*
*@parameventThemotionevent.
*@returnTrueiftheeventwashandled,falseotherwise.
*/
publicbooleanonTouchEvent(MotionEventevent){
finalintviewFlags=mViewFlags;
if((viewFlags&ENABLED_MASK)==DISABLED){
//Adisabledviewthatisclickablestillconsumesthetouch
//events,itjustdoesn'trespondtothem.
return(((viewFlags&CLICKABLE)==CLICKABLE||
(viewFlags&LONG_CLICKABLE)==LONG_CLICKABLE));
}
if(mTouchDelegate!=null){
if(mTouchDelegate.onTouchEvent(event)){
returntrue;
}
}
if(((viewFlags&CLICKABLE)==CLICKABLE||
(viewFlags&LONG_CLICKABLE)==LONG_CLICKABLE)){
switch(event.getAction()){
caseMotionEvent.ACTION_UP:
booleanprepressed=(mPrivateFlags&PREPRESSED)!=0;
if((mPrivateFlags&PRESSED)!=0||prepressed){
//takefocusifwedon'thaveitalreadyandweshouldin
//touchmode.
booleanfocusTaken=false;
if(isFocusable()&&isFocusableInTouchMode()&&!isFocused()){
focusTaken=requestFocus();
}
if(!mHasPerformedLongPress){
//Thisisatap,soremovethelongpresscheck
removeLongPressCallback();
//Onlyperformtakeclickactionsifwewereinthepressedstate
if(!focusTaken){
//UseaRunnableandpostthisratherthancalling
//performClickdirectly.Thisletsothervisualstate
//oftheviewupdatebeforeclickactionsstart.
if(mPerformClick==null){
mPerformClick=newPerformClick();
}
if(!post(mPerformClick)){
performClick();
}
}
}
if(mUnsetPressedState==null){
mUnsetPressedState=newUnsetPressedState();
}
if(prepressed){
mPrivateFlags|=PRESSED;
refreshDrawableState();
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
}elseif(!post(mUnsetPressedState)){
//Ifthepostfailed,unpressrightnow
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
caseMotionEvent.ACTION_DOWN:
if(mPendingCheckForTap==null){
mPendingCheckForTap=newCheckForTap();
}
mPrivateFlags|=PREPRESSED;
mHasPerformedLongPress=false;
postDelayed(mPendingCheckForTap,ViewConfiguration.getTapTimeout());
break;
caseMotionEvent.ACTION_CANCEL:
mPrivateFlags&=~PRESSED;
refreshDrawableState();
removeTapCallback();
break;
caseMotionEvent.ACTION_MOVE:
finalintx=(int)event.getX();
finalinty=(int)event.getY();
//Belenientaboutmovingoutsideofbuttons
intslop=mTouchSlop;
if((x<0-slop)||(x>=getWidth()+slop)||
(y<0-slop)||(y>=getHeight()+slop)){
//Outsidebutton
removeTapCallback();
if((mPrivateFlags&PRESSED)!=0){
//Removeanyfuturelongpress/tapchecks
removeLongPressCallback();
//Needtoswitchfrompressedtonotpressed
mPrivateFlags&=~PRESSED;
refreshDrawableState();
}
}
break;
}
returntrue;
}
returnfalse;
}
<p></p>
1、代码10-15)处理该视图是否为disable状态,如果是,什么都不处理,返回true,即消耗该消息。
2、(代码17-21)消息代理处理消息。所谓的消息代理是指,可以给某个View指定一个消息处理代理,当View收到消息时,首先将该消息派发给其代理进行处理。如果代理内部消耗了该消息,则View不需要在进行任何处理;如果代理没有处理,则view继续处理。在这里不用多管,不会影响事件处理的逻辑和结果。
3、(代码22-102)判断该视图是否可以点击,如果不可以点击,则直接返回false,即不处理该消息。否则,真正开始执行触摸消息的默认处理逻辑,该逻辑分别处理了ACTION_DOWN、ACTION_MOVE、ACTION_UP消息.
(26-70)处理ACTION_UP消息,判断该UP消息是否发生在前面所讲的哪一个监测时间段中,并据此进行不同的处理。
(71-78)在ACTION_DOWN消息中,给mPrivateFlags变量添加PRESSED标识,并将变量mHasPerformLongPress设置为false,然后启动tap监测,即发送一个异步延迟消息。
对于触摸消息而言,消息本身没有repeat的属性,一次触摸只有一个ACTION_DOWN消息,按下来就是连续的ACTION_MOVE消息,并最终以处理ACTION_CANCEL消息.
(80-85)ACTION_CANCLE消息处理,这里只需清除PRESSED标识,刷新视图状态,然后再关闭tap监测即可。
(86-105)对ACTION_MOVE消息进行处理。具体逻辑包括,判断是否移动到了视图区域以外,如果是,则删除tap或者longPress的监测,并清除mPrivateFlags中的PRESSED标识,然后调用refreshDrawableState()刷新视图状态,这将导致对背景状态进行重绘。
五、例子程序
通过代码可能完全理解事件的分发过程有点难,下面通过一个例子程序来巩固下事件分发的过程。
例子程序的UI如下:
布局如下:
[java]view
plain
<org.sunday.main.MyLinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<org.sunday.main.MyLinearLayout
android:id="@+id/ll2"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="@drawable/ll_selector"
android:orientation="vertical">
</org.sunday.main.MyLinearLayout>
<org.sunday.main.MyLinearLayout
android:id="@+id/ll1"
android:layout_width="match_parent"
android:layout_marginTop="20dp"
android:layout_height="100dp"
android:background="@drawable/ll_selector"
android:orientation="vertical">
<org.sunday.main.MyLinearLayout
android:layout_width="80dp"
android:layout_height="50dp"
android:layout_marginTop="20dp"
android:clickable="true"
android:background="@drawable/ll_selector2"
android:orientation="vertical">
</org.sunday.main.MyLinearLayout>
</org.sunday.main.MyLinearLayout>
<org.sunday.main.MyLinearLayout
android:id="@+id/ll3"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginTop="20dp"
android:background="@drawable/ll_selector"
android:orientation="vertical">
<org.sunday.main.MyButton
android:layout_width="wrap_content"
android:layout_height="60dp"
android:layout_margin="20dp"
android:background="@drawable/btn_selector"
android:text="button1"/>
</org.sunday.main.MyLinearLayout>
<org.sunday.main.MyLinearLayout
android:id="@+id/ll4"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginTop="20dp"
android:background="@drawable/ll_selector"
android:orientation="vertical">
<org.sunday.main.MyButtonWithOutEvent
android:layout_width="wrap_content"
android:layout_height="60dp"
android:layout_margin="20dp"
android:background="@drawable/btn_selector"
android:text="button2"/>
</org.sunday.main.MyLinearLayout>
</org.sunday.main.MyLinearLayout>
MyLinearLayout.java
[java]view
plain
packageorg.sunday.main;
importandroid.content.Context;
importandroid.util.AttributeSet;
importandroid.util.Log;
importandroid.view.MotionEvent;
importandroid.widget.LinearLayout;
publicclassMyLinearLayoutextendsLinearLayout{
privatefinalstaticStringTAG="MyLinearLayout";
publicMyLinearLayout(Contextcontext){
super(context);
}
publicMyLinearLayout(Contextcontext,AttributeSetattrs){
super(context,attrs);
}
@Override
publicbooleandispatchTouchEvent(MotionEventev){
Log.e(TAG,"dispatchTouchEvent()----------------"+this.toString());
booleanisTrue=super.dispatchTouchEvent(ev);
Log.e(TAG,"dispatchTouchEvent()isTrue="+isTrue+"---------------;"+this.toString());
returnisTrue;
}
@Override
publicbooleanonTouchEvent(MotionEventevent){
Log.e(TAG,"onTouchEvent()----------------"+this.toString());
booleanisTrue=super.onTouchEvent(event);
Log.e(TAG,"onTouchEvent()isTrue="+isTrue+"-----------------;"+this.toString());
returnisTrue;
}
}
MyButton.java
[java]view
plain
packageorg.sunday.main;
importandroid.content.Context;
importandroid.util.AttributeSet;
importandroid.util.Log;
importandroid.view.MotionEvent;
importandroid.widget.Button;
publicclassMyButtonextendsButton{
privatefinalstaticStringTAG="MyButton";
publicMyButton(Contextcontext){
super(context);
}
publicMyButton(Contextcontext,AttributeSetattrs){
super(context,attrs);
}
@Override
publicbooleandispatchTouchEvent(MotionEventevent){
Log.e(TAG,"dispatchTouchEvent()----------------");
booleanisTrue=super.dispatchTouchEvent(event);
Log.e(TAG,"dispatchTouchEventisTrue="+isTrue);
returnisTrue;
}
@Override
publicbooleanonTouchEvent(MotionEventevent){
Log.e(TAG,"onTouchEvent()----------------");
booleanisTrue=super.onTouchEvent(event);
Log.e(TAG,"onTouchEventisTrue="+isTrue);
returnisTrue;
}
}
MyButtonWithOutEvent.java
[java]view
plain
packageorg.sunday.main;
importandroid.content.Context;
importandroid.util.AttributeSet;
importandroid.util.Log;
importandroid.view.MotionEvent;
importandroid.widget.Button;
publicclassMyButtonWithOutEventextendsButton{
privatefinalstaticStringTAG="MyButtonWithOutEvent";
publicMyButtonWithOutEvent(Contextcontext){
super(context);
}
publicMyButtonWithOutEvent(Contextcontext,AttributeSetattrs){
super(context,attrs);
}
@Override
publicbooleandispatchTouchEvent(MotionEventevent){
Log.e(TAG,"dispatchTouchEvent()----------------"+this.toString());
//booleanisTrue=super.dispatchTouchEvent(event);
//Log.e(TAG,"isTrue="+isTrue);
returnfalse;
}
@Override
publicbooleanonTouchEvent(MotionEventevent){
Log.e(TAG,"onTouchEvent()----------------");
booleanisTrue=super.onTouchEvent(event);
Log.e(TAG,"onTouchEventisTrue="+isTrue);
returnisTrue;
}
}
点击例子一,后台打印的日志如下:
解释:例子一的布局中只有两个ViewGroup子类对象,当点击例子一区域,根ViewGroup首先分发touch事件,因为它有child,所以接着会将事件传递到它的孩子ViewGroup。孩子ViewGroup发现自己没有child了,所以它就得自己处理这个touch事件。所以会调用OnTouchEvent()方法来处理这个事件。处理完之后会返回一个true,一直返回给根ViewGroup。
点击例子二的绿色区域,后台打印日志如下:
解释:例子二和例子一差不多,只不过多了一个ViewGroup而已。
点击例子三得黄色区域,后台打印日志如下:
解释:例子三相比于例子二只是将最上层的ViewGroup换成了View,原理一样。
点击例子四的黄色区域,打印日志如下:
解释:在这个程序中,对于MyLinearLayout和MyButton的dispachTouchEvent()和onTouchEvent()发放的逻辑并没有进行任何的修改。而对于MyButtonWithOutEvent,在dispatchTouchEvent()方法中,直接返回的false(即不进行事件的任何处理),那么事件传递到这里时,它将会把事件返回给调用它的父ViewGroup进行处理,所以这里我们看到的是MyLinearLayout调用了onTouchEvent()。
点击例子三得button和点击例子四的button效果图如下:
例子三中是button响应了这个touch事件,而在例子四中则是LinearLayout响应了这个touch事件。
demo:
================================
其他参考链接
图解View的事件分发机制
http://www.tuicool.com/articles/ZVbUnme
Andriod从源码的角度详解View,ViewGroup的Touch事件的分发机制
http://my.oschina.net/u/1538627/blog/217609?p={{totalPage}}
《Android深入透析》之Android事件分发机制
/article/5419333.html
AndroidTouch事件传递机制解析
/article/5050759.html
相关文章推荐
- Codeforces Round #335 (Div. 2)-Sorting Railway Cars(求连续的上升序列的最大值)
- 走迷宫机器人程序之3--main
- 正在创造“情感计算”的微软小冰,或将开启未来计算新纪元
- 中国雾霾 China smog_ Sky dark from air pollution
- LightOJ 1071 - Baker Vai(水DP)
- Factorial Trailing Zeroes
- Setup MIPS cross compile toolchain
- Codeforces Round #335 (Div. 2) C. Sorting Railway Cars 动态规划
- Climbing Stairs
- (BUG) SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder"问题解决 .
- 性能测试分享: Jmeter的源码分析main函数参数
- 【Codeforces Round 335 (Div 2) C】【贪心 脑洞 有趣排序】Sorting Railway Cars 全排列排序可以提前后提后的最小操作数
- container error log
- Air Kiss(飞吻)技术实现方案
- LeetCode - 11. Container With Most Water
- hdu 5029 Relief grain
- aidl和ICP
- aidl和ICP
- adbe\.exestart-server failed--run manually if necessary
- adbe\.exestart-server failed--run manually if necessary