AndroidTouch事件分发过程详解
本文以实例形式讲述了AndroidTouch事件分发过程,对于深入理解与掌握Android程序设计有很大的帮助作用。具体分析如下:
首先,从一个简单示例入手:
先看一个示例如下图所示:
布局文件:
<FrameLayoutxmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="center" tools:context="com.example.touch_event.MainActivity" tools:ignore="MergeRootFrame"> <Button android:id="@+id/my_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hello_world"/> </FrameLayout>
MainActivity文件:
publicclassMainActivityextendsActivity{ @Override protectedvoidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButtonmBtn=(Button)findViewById(R.id.my_button); mBtn.setOnTouchListener(newOnTouchListener(){ @Override publicbooleanonTouch(Viewv,MotionEventevent){ Log.d("","###onTouch:"+event.getAction()); returnfalse; } }); mBtn.setOnClickListener(newOnClickListener(){ @Override publicvoidonClick(Viewv){ Log.d("","###onClick:"+v); } }); } @Override publicbooleandispatchTouchEvent(MotionEventev){ Log.d("","###activitydispatchTouchEvent"); returnsuper.dispatchTouchEvent(ev); } }
当用户点击按钮时会输出如下Log:
08-3103:03:56.116:D/(1560):###activitydispatchTouchEvent 08-3103:03:56.116:D/(1560):###onTouch:0 08-3103:03:56.196:D/(1560):###activitydispatchTouchEvent 08-3103:03:56.196:D/(1560):###onTouch:1 08-3103:03:56.196:D/(1560):###onClick:android.widget.Button{52860d98VFED..C....PH...0,0-1080,144#7f05003dapp:id/my_button}
我们可以看到首先执行了Activity中的dispatchTouchEvent方法,然后执行了onTouch方法,然后再是dispatchTouchEvent-->onTouch,最后才是执行按钮的点击事件。这里我们可能有个疑问,为什么dispatchTouchEvent和onTouch都执行了两次,而onClick才执行了一次?为什么两次的Touch事件的action不一样,action0和action1到底代表了什么?
覆写过onTouchEvent的朋友知道,一般来说我们在该方法体内都会处理集中touch类型的事件,有ACTION_DOWN、ACTION_MOVE、ACTION_UP等,不过上面我们的例子中并没有移动,只是单纯的按下、抬起。因此,我们的触摸事件也只有按下、抬起,因此有2次touch事件,而action分别为0和1。我们看看MotionEvent中的一些变量定义吧:
publicfinalclassMotionEventextendsInputEventimplementsParcelable{ //代码省略 publicstaticfinalintACTION_DOWN=0;//按下事件 publicstaticfinalintACTION_UP=1;//抬起事件 publicstaticfinalintACTION_MOVE=2;//手势移动事件 publicstaticfinalintACTION_CANCEL=3;//取消 //代码省略 }
可以看到,代表按下的事件为0,抬起事件为1,也证实了我们上面所说的。
在看另外两个场景:
1、我们点击按钮外的区域,输出Log如下:
08-3103:04:45.408:D/(1560):###activitydispatchTouchEvent08-31 03:04:45.512:D/(1560):###activitydispatchTouchEvent
2、我们在onTouch函数中返回true,输出Log如下:
08-3103:06:04.764:D/(1612):###activitydispatchTouchEvent 08-3103:06:04.764:D/(1612):###onTouch:0 08-3103:06:04.868:D/(1612):###activitydispatchTouchEvent 08-3103:06:04.868:D/(1612):###onTouch:1
以上两个场景为什么会这样呢? 我们继续往下看吧。
AndroidTouch事件分发
那么整个事件分发的流程是怎样的呢?
简单来说就是用户触摸手机屏幕会产生一个触摸消息,最终这个触摸消息会被传送到ViewRoot(看4.2的源码时这个类改成了ViewRootImpl)的InputHandler,ViewRoot是GUI管理系统与GUI呈现系统之间的桥梁,根据ViewRoot的定义,发现它并不是一个View类型,而是一个Handler。InputHandler是一个接口类型,用于处理KeyEvent和TouchEvent类型的事件,我们看看源码:
publicfinalclassViewRootextendsHandlerimplementsViewParent, View.AttachInfo.Callbacks{ //代码省略 privatefinalInputHandlermInputHandler=newInputHandler(){ publicvoidhandleKey(KeyEventevent,RunnablefinishedCallback){ startInputEvent(finishedCallback); dispatchKey(event,true); } publicvoidhandleMotion(MotionEventevent,RunnablefinishedCallback){ startInputEvent(finishedCallback); dispatchMotion(event,true);//1、handle触摸消息 } }; //代码省略 //2、分发触摸消息 privatevoiddispatchMotion(MotionEventevent,booleansendDone){ intsource=event.getSource(); if((source&InputDevice.SOURCE_CLASS_POINTER)!=0){ dispatchPointer(event,sendDone);//分发触摸消息 }elseif((source&InputDevice.SOURCE_CLASS_TRACKBALL)!=0){ dispatchTrackball(event,sendDone); }else{ //TODO Log.v(TAG,"Droppingunsupportedmotionevent(unimplemented):"+event); if(sendDone){ finishInputEvent(); } } } //3、通过Handler投递消息 privatevoiddispatchPointer(MotionEventevent,booleansendDone){ Messagemsg=obtainMessage(DISPATCH_POINTER); msg.obj=event; msg.arg1=sendDone?1:0; sendMessageAtTime(msg,event.getEventTime()); } @Override publicvoidhandleMessage(Messagemsg){//ViewRoot覆写handlerMessage来处理各种消息 switch(msg.what){ //代码省略 caseDO_TRAVERSAL: if(mProfile){ Debug.startMethodTracing("ViewRoot"); } performTraversals(); if(mProfile){ Debug.stopMethodTracing(); mProfile=false; } break; caseDISPATCH_POINTER:{//4、处理DISPATCH_POINTER类型的消息,即触摸屏幕的消息 MotionEventevent=(MotionEvent)msg.obj; try{ deliverPointerEvent(event);//5、处理触摸消息 }finally{ event.recycle(); if(msg.arg1!=0){ finishInputEvent(); } if(LOCAL_LOGV||WATCH_POINTER)Log.i(TAG,"Donedispatching!"); } }break; //代码省略 } //6、真正的处理事件 privatevoiddeliverPointerEvent(MotionEventevent){ if(mTranslator!=null){ mTranslator.translateEventInScreenToAppWindow(event); } booleanhandled; if(mView!=null&&mAdded){ //entertouchmodeonthedown booleanisDown=event.getAction()==MotionEvent.ACTION_DOWN; if(isDown){ ensureTouchMode(true);//如果是ACTION_DOWN事件则进入触摸模式,否则为按键模式。 } if(Config.LOGV){ captureMotionLog("captureDispatchPointer",event); } if(mCurScrollY!=0){ event.offsetLocation(0,mCurScrollY);//物理坐标向逻辑坐标的转换 } if(MEASURE_LATENCY){ lt.sample("ADispatchingTouchEvents",System.nanoTime()-event.getEventTimeNano()); } //7、分发事件,如果是窗口类型,则这里的mView对应的就是PhonwWindow中的DecorView,否则为根视图的ViewGroup。 handled=mView.dispatchTouchEvent(event); //代码省略 } } //代码省略 }
经过层层迷雾,不管代码7处的mView是DecorView还是非窗口界面的根视图,其本质都是ViewGroup,即触摸事件最终被根视图ViewGroup进行分发!!!
我们就以Activity为例来分析这个过程,我们知道显示出来的Activity有一个顶层窗口,这个窗口的实现类是PhoneWindow,PhoneWindow中的内容区域是一个DecorView类型的View,这个View这就是我们在手机上看到的内容,这个DecorView是FrameLayout的子类,Activity的的dispatchTouchEvent实际上就是调用PhoneWindow的dispatchTouchEvent,我们看看源代码吧,进入Activity的dispatchTouchEvent函数:
publicbooleandispatchTouchEvent(MotionEventev){ if(ev.getAction()==MotionEvent.ACTION_DOWN){ onUserInteraction(); } if(getWindow().superDispatchTouchEvent(ev)){//1、调用的是PhoneWindow的superDispatchTouchEvent(ev) returntrue; } returnonTouchEvent(ev); } publicvoidonUserInteraction(){ }
可以看到,如果事件为按下事件,则会进入到onUserInteraction()这个函数,该函数为空实现,我们暂且不管它。继续看,发现touch事件的分发调用了getWindow().superDispatchTouchEvent(ev)函数,getWindow()获取到的实例的类型为PhoneWindow类型,你可以在你的Activity类中使用如下方式查看getWindow()获取到的类型:
Log.d("","###Activiti中getWindow()获取的类型是:"+this.getWindow());
输出:
08-3103:40:17.036:D/(1688):###Activiti中getWindow()获取的类型是:com.android.internal.policy.impl.PhoneWindow@5287fe38
OK,废话不多说,我们还是继续看PhoneWindow中的superDispatchTouchEvent函数吧。
@Override publicbooleansuperDispatchTouchEvent(MotionEventevent){ returnmDecor.superDispatchTouchEvent(event); }
恩,调用的是mDecor的superDispatchTouchEvent(event)函数,这个mDecor就是我们上面所说的DecorView类型,也就是我们看到的Activity上的所有内容的一个顶层ViewGroup,即整个ViewTree的根节点。看看它的声明吧。
//Thisisthetop-levelviewofthewindow,containingthewindowdecor. privateDecorViewmDecor;
DecorView
那么我继续看看DecorView到底是个什么玩意儿吧。
privatefinalclassDecorViewextendsFrameLayoutimplementsRootViewSurfaceTaker{ /*package*/intmDefaultOpacity=PixelFormat.OPAQUE; /**ThefeatureIDofthepanel,or-1ifthisistheapplication"sDecorView*/ privatefinalintmFeatureId; privatefinalRectmDrawingBounds=newRect(); privatefinalRectmBackgroundPadding=newRect(); privatefinalRectmFramePadding=newRect(); privatefinalRectmFrameOffsets=newRect(); privatebooleanmChanging; privateDrawablemMenuBackground; privatebooleanmWatchingForMenu; privateintmDownY; publicDecorView(Contextcontext,intfeatureId){ super(context); mFeatureId=featureId; } @Override publicbooleandispatchKeyEvent(KeyEventevent){ finalintkeyCode=event.getKeyCode(); //代码省略 returnisDown?PhoneWindow.this.onKeyDown(mFeatureId,event.getKeyCode(),event) :PhoneWindow.this.onKeyUp(mFeatureId,event.getKeyCode(),event); } @Override publicbooleandispatchTouchEvent(MotionEventev){ finalCallbackcb=getCallback(); returncb!=null&&mFeatureId<0?cb.dispatchTouchEvent(ev):super .dispatchTouchEvent(ev); } @Override publicbooleandispatchTrackballEvent(MotionEventev){ finalCallbackcb=getCallback(); returncb!=null&&mFeatureId<0?cb.dispatchTrackballEvent(ev):super .dispatchTrackballEvent(ev); } publicbooleansuperDispatchKeyEvent(KeyEventevent){ returnsuper.dispatchKeyEvent(event); } publicbooleansuperDispatchTouchEvent(MotionEventevent){ returnsuper.dispatchTouchEvent(event); } publicbooleansuperDispatchTrackballEvent(MotionEventevent){ returnsuper.dispatchTrackballEvent(event); } @Override publicbooleanonTouchEvent(MotionEventevent){ returnonInterceptTouchEvent(event); } //代码省略 }
可以看到,DecorView继承自FrameLayout,它对于touch事件的分发(dispatchTouchEvent)、处理都是交给super类来处理,也就是FrameLayout来处理,我们在FrameLayout中没有看到相应的实现,那继续跟踪到FrameLayout的父类,即ViewGroup,我们看到了dispatchTouchEvent的实现,那我们就先看ViewGroup(Android2.3源码)是如何进行事件分发的吧。
ViewGroup的Touch事件分发
/** *{@inheritDoc} */ @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))//1、是否禁用拦截、是否拦截事件 //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--)//2、迭代所有子view,查找触摸事件在哪个子view的坐标范围内 finalViewchild=children[i]; if((child.mViewFlags&VISIBILITY_MASK)==VISIBLE ||child.getAnimation()!=null){ child.getHitRect(frame);//3、获取child的坐标范围 if(frame.contains(scrolledXInt,scrolledYInt))//4、判断发生该事件坐标是否在该child坐标范围内 //offsettheeventtotheview"scoordinatesystem finalfloatxc=scrolledXFloat-child.mLeft; finalfloatyc=scrolledYFloat-child.mTop; ev.setLocation(xc,yc); child.mPrivateFlags&=~CANCEL_NEXT_UP_EVENT; if(child.dispatchTouchEvent(ev))//5、child处理该事件 //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处可以看到一个条件判断,如果disallowIntercept和!onInterceptTouchEvent(ev)两者有一个为true,就会进入到这个条件判断中。disallowIntercept是指是否禁用掉事件拦截的功能,默认是false,也可以通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改。那么当第一个值为false的时候就会完全依赖第二个值来决定是否可以进入到条件判断的内部,第二个值是什么呢?onInterceptTouchEvent就是ViewGroup对事件进行拦截的一个函数,返回该函数返回false则表示不拦截事件,反之则表示拦截。第二个条件是是对onInterceptTouchEvent方法的返回值取反,也就是说如果我们在onInterceptTouchEvent方法中返回false,就会让第二个值为true,从而进入到条件判断的内部,如果我们在onInterceptTouchEvent方法中返回true,就会让第二个值的整体变为false,从而跳出了这个条件判断。例如我们需要实现ListView滑动删除某一项的功能,那么可以通过在onInterceptTouchEvent返回true,并且在onTouchEvent中实现相关的判断逻辑,从而实现该功能。
进入代码1内部的if后,有一个for循环,遍历了当前ViewGroup下的所有子childview,如果触摸该事件的坐标在某个childview的坐标范围内,那么该childview来处理这个触摸事件,即调用该childview的dispatchTouchEvent。如果该childview是ViewGroup类型,那么继续执行上面的判断,并且遍历子view;如果该childview不是ViewGroup类型,那么直接调用的是View中的dispatchTouchEvent方法,除非这个childview的类型覆写了该方法。我们看看View中的dispatchTouchEvent函数:
View的Touch事件分发
/** *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); }
该函数中,首先判断该事件是否符合安全策略,然后判断该view是否是enable的,以及是否设置了TouchListener,mOnTouchListener即我们通过setOnTouchListener设置的。
/** *Registeracallbacktobeinvokedwhenatoucheventissenttothisview. *@paramlthetouchlistenertoattachtothisview */ publicvoidsetOnTouchListener(OnTouchListenerl){ mOnTouchListener=l; }
如果mOnTouchListener.onTouch(this,event)返回false则继续执行onTouchEvent(event);如果mOnTouchListener.onTouch(this,event)返回true,则表示该事件被消费了,不再传递,因此也不会执行onTouchEvent(event)。这也验证了我们上文中留下的场景2,当onTouch函数返回true时,点击按钮,但我们的点击事件没有执行。那么我们还是先来看看onTouchEvent(event)函数到底做了什么吧。
/** *Implementthismethodtohandletouchscreenmotionevents. * *@parameventThemotionevent. *@returnTrueiftheeventwashandled,falseotherwise. */ publicbooleanonTouchEvent(MotionEventevent){ finalintviewFlags=mViewFlags; if((viewFlags&ENABLED_MASK)==DISABLED)//1、判断该view是否enable //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))//2、是否是clickable或者longclickable 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))//post performClick();//3、点击事件处理 } } } 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; }
我们看到,在onTouchEvent函数中就是对ACTION_UP、ACTION_DOWN、ACTION_MOVE等几个事件进行处理,而最重要的就是UP事件了,因为这个里面包含了对用户点击事件的处理,或者是说对于用户而言相对重要一点,因此放在了第一个case中。在ACTION_UP事件中会判断该view是否enable、是否clickable、是否获取到了焦点,然后我们看到会通过post方法将一个PerformClick对象投递给UI线程,如果投递失败则直接调用performClick函数执行点击事件。
/** *CausestheRunnabletobeaddedtothemessagequeue. *Therunnablewillberunontheuserinterfacethread. * *@paramactionTheRunnablethatwillbeexecuted. * *@returnReturnstrueiftheRunnablewassuccessfullyplacedintothe *messagequeue.Returnsfalseonfailure,usuallybecausethe *looperprocessingthemessagequeueisexiting. */ publicbooleanpost(Runnableaction){ Handlerhandler; if(mAttachInfo!=null){ handler=mAttachInfo.mHandler; }else{ //Assumethatpostwillsucceedlater ViewRoot.getRunQueue().post(action); returntrue; } returnhandler.post(action); }
我们看看PerformClick类吧。
privatefinalclassPerformClickimplementsRunnable{ publicvoidrun(){ performClick(); } }
可以看到,其内部就是包装了View类中的performClick()方法。再看performClick()方法:
/** *Registeracallbacktobeinvokedwhenthisviewisclicked.Ifthisviewisnot *clickable,itbecomesclickable. * *@paramlThecallbackthatwillrun * *@see#setClickable(boolean) */ publicvoidsetOnClickListener(OnClickListenerl){ if(!isClickable()){ setClickable(true); } mOnClickListener=l; } /** *Callthisview"sOnClickListener,ifitisdefined. * *@returnTruetherewasanassignedOnClickListenerthatwascalled,false *otherwiseisreturned. */ publicbooleanperformClick(){ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); if(mOnClickListener!=null){ playSoundEffect(SoundEffectConstants.CLICK); mOnClickListener.onClick(this); returntrue; } returnfalse; }
代码很简单,主要就是调用了mOnClickListener.onClick(this);方法,即执行用户通过setOnClickListener设置进来的点击事件处理Listener。
总结
用户触摸屏幕产生一个触摸消息,系统底层将该消息转发给ViewRoot(ViewRootImpl),ViewRoot产生一个DISPATCHE_POINTER的消息,并且在handleMessage中处理该消息,最终会通过deliverPointerEvent(MotionEventevent)来处理该消息。在该函数中会调用mView.dispatchTouchEvent(event)来分发消息,该mView是一个ViewGroup类型,因此是ViewGroup的dispatchTouchEvent(event),在该函数中会遍历所有的childview,找到该事件的触发的左边与每个childview的坐标进行对比,如果触摸的坐标在该childview的范围内,则由该childview进行处理。如果该childview是ViewGroup类型,则继续上一步的查找过程;否则执行View中的dispatchTouchEvent(event)函数。在View的dispatchTouchEvent(event)中首先判断该控件是否enale以及mOnTouchListent是否为空,如果mOnTouchListener不为空则执行mOnTouchListener.onTouch(event)方法,如果该方法返回false则再执行View中的onTouchEvent(event)方法,并且在该方法中执行mOnClickListener.onClick(this,event);方法;如果mOnTouchListener.onTouch(event)返回true则不会执行onTouchEvent方法,因此点击事件也不会被执行。
粗略的流程图如下:
相信本文所述对大家进一步深入掌握Android程序设计有一定的借鉴价值。
相关文章
- jquery事件delegate()方法用法详解[通俗易懂]
- 2023年春节期间XR资讯大事件合集
- ViewGroup/View 事件分发和疑惑详解手机开发
- 事件代理 方式百度换皮肤详解编程语言
- 五子棋游戏 canvas 事件 边界检测详解编程语言
- [javascript] 看知乎学习js事件触发过程详解编程语言
- JQuery 的Bind()事件详解编程语言
- JavaScript事件委托详解编程语言
- onbeforeunload事件详解编程语言
- Oracle 等待事件 gc buffer busy release 官方解释,作用,如何使用及优化方法
- jQuery事件详解编程语言
- OO ALV中的data_changed、data_changed_finished事件介绍详解编程语言
- MySQL查看事件
- 探索Linux操作系统的事件 Event0(linuxevent0)
- DigitalOcean宕机事件回顾:主数据库被删除,四小时后恢复
- 如何应对MySQL删库事件并进行数据恢复?(mysql删库恢复)
- MySQL事件的应用与作用详解(mysql中事件的作用)
- Oracle事件详解解码提升数据库性能(oracle事件详细解析)
- Oracle事件号保护你的财产(oracle事件号)
- 实例区别onClick和onDBClick两事件方法
- 表单元素事件(FormElementEvents)
- JavaScript高级程序设计阅读笔记(十八)js跨平台的事件
- window.location.href=window.location.href跳转无反应a超链接onclick事件写法
- js的onload事件及初始化按钮事件示例代码
- javascript阻止scroll事件多次执行的思路及实现
- Js冒泡事件详解及阻止示例
- Node.js中HTTP模块与事件模块详解
- C#微信公众号开发之接收事件推送与消息排重的方法