您的位置:首页 > 大数据 > 人工智能

View工作原理(一)事件传递原理详解http://blog.csdn.net/ff20081528/article/details/17353869

2015-12-10 20:06 501 查看
一、准备知识

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
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,则意味着诋毁调用的结束。
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
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:点击打开链接

================================

其他参考链接


图解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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: