android事件分发机制的实现原理
android中的事件处理,以及解决滑动冲突问题都离不开事件分发机制,android中的事件流,即MotionEvent都会经历一个从分发,拦截到处理的一个过程。即dispatchTouchEvent(),onInterceptEvent()到onTouchEvent()的一个过程,在dispatchTouchEvent()负责了事件的分发过程,在dispatchTouchEvent()中会调用onInterceptEvent()与onTouchEvent(),如果onInterceptEvent()返回true,那么会调用到当前view的onTouchEvent()方法,如果不拦截,事件就会下发到子view的dispatchTouchEvent()中进行同样的操作。本文将带领大家从源码角度来分析android是如何进行事件分发的。
android中的事件分发流程最先从activity的dispatchTouchEvent()开始:
publicbooleandispatchTouchEvent(MotionEventev){ if(ev.getAction()==MotionEvent.ACTION_DOWN){ onUserInteraction(); } if(getWidow().superDispatchTouchEvent(ev)){ returntrue; } returnonTouchEvent(ev); }
这里调用了getWindow().superDispatchTouchEvent(ev),这里可以看出activity将MotionEvent传寄给了Window。而Window是一个抽象类,superDispatchTouchEvent()也是一个抽象方法,这里用到的是window的子类phoneWindow。
@Override publicbooleansuperDispatchTouchEvent(MotionEventevent){ returnmDecor.superDispatchTouchEvent(event); }
从这里可以看出,event事件被传到了DecorView,也就是我们的顶层view.我们继续跟踪:
publicbooleansuperDispatchTouchEvent(MotionEventevent){ returnsuper.dispatchTouchEvent(event); }
这里调用到了父类的dispatchTouchEvent()方法,而DecorView是继承自FrameLayout,FrameLayout继承了ViewGroup,所以这里会调用到ViewGroup的dispatchTouchEvent()方法。
所以整个事件流从activity开始,传递到window,最后再到我们的view(viewGroup也是继承自view)中,而view才是我们整个事件处理的核心阶段。
我们来看一下viewGroup的dispatchTouchEvent()中的实现:
if(actionMasked==MotionEvent.ACTION_DOWN){ //Throwawayallpreviousstatewhenstartinganewtouchgesture. //Theframeworkmayhavedroppedtheuporcanceleventforthepreviousgesture //duetoanappswitch,ANR,orsomeotherstatechange. cancelAndClearTouchTargets(ev); resetTouchState(); }
这是dispatchTouchEvent()开始时截取的一段代码,我们来看一下,首先,当我们手指按下view时,会调用到resetTouchState()方法,在resetTouchState()中:
privatevoidresetTouchState(){ clearTouchTargets(); resetCancelNextUpFlag(this); mGroupFlags&=~FLAG_DISALLOW_INTERCEPT; mNestedScrollAxes=SCROLL_AXIS_NONE; }
我们继续跟踪clearTouchTargets()方法:
privatevoidclearTouchTargets(){ TouchTargettarget=mFirstTouchTarget; if(target!=null){ do{ TouchTargetnext=target.next; target.recycle(); target=next; }while(target!=null); mFirstTouchTarget=null; } }
在clearTouchTargets()方法中,我们最终将mFirstTouchTarget赋值为null,我们继续回到dispatchTouchEvent()中,接着执行了下段代码:
//Checkforinterception. finalbooleanintercepted; if(actionMasked==MotionEvent.ACTION_DOWN ||mFirstTouchTarget!=null){ finalbooleandisallowIntercept=(mGroupFlags&FLAG_DISALLOW_INTERCEPT)!=0; if(!disallowIntercept){ intercepted=onInterceptTouchEvent(ev); ev.setAction(action);//restoreactionincaseitwaschanged }else{ intercepted=false; } }else{ //Therearenotouchtargetsandthisactionisnotaninitialdown //sothisviewgroupcontinuestointercepttouches. intercepted=true; }
当view被按下或mFirstTouchTarget!=null的时候,从前面可以知道,当每次view被按下时,也就是重新开始一次事件流的处理时,mFirstTouchTarget都会被设置成null,一会我们看mFirstTouchTarget是什么时候被赋值的。
从disallowIntercept属性我们大概能猜到是用来判断是否需要坐拦截处理,而我们知道可以通过调用父view的requestDisallowInterceptTouchEvent(true)可以让我们的父view不能对事件进行拦截,我们先来看看requestDisallowInterceptTouchEvent()方法中的实现:
@Override publicvoidrequestDisallowInterceptTouchEvent(booleandisallowIntercept){ if(disallowIntercept==((mGroupFlags&FLAG_DISALLOW_INTERCEPT)!=0)){ //We'realreadyinthisstate,assumeourancestorsaretoo return; } if(disallowIntercept){ mGroupFlags|=FLAG_DISALLOW_INTERCEPT; }else{ mGroupFlags&=~FLAG_DISALLOW_INTERCEPT; } //Passituptoourparent if(mParent!=null){ mParent.requestDisallowInterceptTouchEvent(disallowIntercept); } }
这里也是通过设置标志位做判断处理,所以这里是通过改变mGroupFlags标志,然后在dispatchTouchEvent()刚发中变更disallowIntercept的值判断是否拦截,当为true时,即需要拦截,这个时候便会跳过onInterceptTouchEvent()拦截判断,并标记为不拦截,即intercepted=false,我们继续看viewGroup的onInterceptTouchEvent()处理:
publicbooleanonInterceptTouchEvent(MotionEventev){ if(ev.isFromSource(InputDevice.SOURCE_MOUSE) &&ev.getAction()==MotionEvent.ACTION_DOWN &&ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY) &&isOnScrollbarThumb(ev.getX(),ev.getY())){ returntrue; } returnfalse; }
即默认情况下,只有在ACTION_DOWN时,viewGroup才会表现为拦截。
我们继续往下看:
finalintchildrenCount=mChildrenCount; if(newTouchTarget==null&&childrenCount!=0){ finalfloatx=ev.getX(actionIndex); finalfloaty=ev.getY(actionIndex); //Findachildthatcanreceivetheevent. //Scanchildrenfromfronttoback. finalArrayListpreorderedList=buildTouchDispatchChildList(); finalbooleancustomOrder=preorderedList==null &&isChildrenDrawingOrderEnabled(); finalView[]children=mChildren; for(inti=childrenCount-1;i>=0;i--){ finalintchildIndex=getAndVerifyPreorderedIndex( childrenCount,i,customOrder); finalViewchild=getAndVerifyPreorderedView( preorderedList,children,childIndex); //Ifthereisaviewthathasaccessibilityfocuswewantit //togettheeventfirstandifnothandledwewillperforma //normaldispatch.Wemaydoadoubleiterationbutthisis //safergiventhetimeframe. if(childWithAccessibilityFocus!=null){ if(childWithAccessibilityFocus!=child){ continue; } childWithAccessibilityFocus=null; i=childrenCount-1; } if(!canViewReceivePointerEvents(child) ||!isTransformedTouchPointInView(x,y,child,null)){ ev.setTargetAccessibilityFocus(false); continue; } newTouchTarget=getTouchTarget(child); if(newTouchTarget!=null){ //Childisalreadyreceivingtouchwithinitsbounds. //Giveitthenewpointerinadditiontotheonesitishandling. newTouchTarget.pointerIdBits|=idBitsToAssign; break; } resetCancelNextUpFlag(child); if(dispatchTransformedTouchEvent(ev,false,child,idBitsToAssign)){ //Childwantstoreceivetouchwithinitsbounds. mLastTouchDownTime=ev.getDownTime(); if(preorderedList!=null){ //childIndexpointsintopresortedlist,findoriginalindex for(intj=0;j 这段代码首先会通过一个循环去遍历所有的子view,最终会调用到dispatchTransformedTouchEvent()方法,我们继续看dispatchTransformedTouchEvent()的实现:
privatebooleandispatchTransformedTouchEvent(MotionEventevent,booleancancel, Viewchild,intdesiredPointerIdBits){ finalbooleanhandled; //Cancelingmotionsisaspecialcase.Wedon'tneedtoperformanytransformations //orfiltering.Theimportantpartistheaction,notthecontents. finalintoldAction=event.getAction(); if(cancel||oldAction==MotionEvent.ACTION_CANCEL){ event.setAction(MotionEvent.ACTION_CANCEL); if(child==null){ handled=super.dispatchTouchEvent(event); }else{ handled=child.dispatchTouchEvent(event); } event.setAction(oldAction); returnhandled; } //Calculatethenumberofpointerstodeliver. finalintoldPointerIdBits=event.getPointerIdBits(); finalintnewPointerIdBits=oldPointerIdBits&desiredPointerIdBits; //Ifforsomereasonweendedupinaninconsistentstatewhereitlookslikewe //mightproduceamotioneventwithnopointersinit,thendroptheevent. if(newPointerIdBits==0){ returnfalse; } //Ifthenumberofpointersisthesameandwedon'tneedtoperformanyfancy //irreversibletransformations,thenwecanreusethemotioneventforthis //dispatchaslongaswearecarefultorevertanychangeswemake. //Otherwiseweneedtomakeacopy. finalMotionEventtransformedEvent; if(newPointerIdBits==oldPointerIdBits){ if(child==null||child.hasIdentityMatrix()){ if(child==null){ handled=super.dispatchTouchEvent(event); }else{ finalfloatoffsetX=mScrollX-child.mLeft; finalfloatoffsetY=mScrollY-child.mTop; event.offsetLocation(offsetX,offsetY); handled=child.dispatchTouchEvent(event); event.offsetLocation(-offsetX,-offsetY); } returnhandled; } transformedEvent=MotionEvent.obtain(event); }else{ transformedEvent=event.split(newPointerIdBits); } //Performanynecessarytransformationsanddispatch. if(child==null){ handled=super.dispatchTouchEvent(transformedEvent); }else{ finalfloatoffsetX=mScrollX-child.mLeft; finalfloatoffsetY=mScrollY-child.mTop; transformedEvent.offsetLocation(offsetX,offsetY); if(!child.hasIdentityMatrix()){ transformedEvent.transform(child.getInverseMatrix()); } handled=child.dispatchTouchEvent(transformedEvent); } //Done. transformedEvent.recycle(); returnhandled; }这段代码就比较明显了,如果child不为null,始终会调用到child.dispatchTouchEvent();否则调用super.dispatchTouchEvent();
如果child不为null时,事件就会向下传递,如果子view处理了事件,即dispatchTransformedTouchEvent()即返回true。继续向下执行到addTouchTarget()方法,我们继续看addTouchTarget()方法的执行结果:
privateTouchTargetaddTouchTarget(@NonNullViewchild,intpointerIdBits){ finalTouchTargettarget=TouchTarget.obtain(child,pointerIdBits); target.next=mFirstTouchTarget; mFirstTouchTarget=target; returntarget; }这个时候我们发现mFirstTouchTarget又出现了,这时候会给mFirstTouchTarget重新赋值,即mFirstTouchTarget不为null。也就是说,如果事件被当前view或子view消费了,那么在接下来的ACTION_MOVE或ACTION_UP事件中,mFirstTouchTarget就不为null。但如果我们继承了该viewGroup,并在onInterceptTouchEvent()的ACTION_MOVE中拦截了事件,那么后续事件将不会下发,将由该viewGroup直接处理,从下面代码我们可以得到:
//Dispatchtotouchtargets,excludingthenewtouchtargetifwealready //dispatchedtoit.Canceltouchtargetsifnecessary. TouchTargetpredecessor=null; TouchTargettarget=mFirstTouchTarget; while(target!=null){ finalTouchTargetnext=target.next; if(alreadyDispatchedToNewTouchTarget&&target==newTouchTarget){ handled=true; }else{ finalbooleancancelChild=resetCancelNextUpFlag(target.child) ||intercepted; if(dispatchTransformedTouchEvent(ev,cancelChild, target.child,target.pointerIdBits)){ handled=true; } if(cancelChild){ if(predecessor==null){ mFirstTouchTarget=next; }else{ predecessor.next=next; } target.recycle(); target=next; continue; } } predecessor=target; target=next; }当存在子view并且事件被子view消费时,即在ACTION_DOWN阶段mFirstTouchTarget会被赋值,即在接下来的ACTION_MOVE事件中,由于intercepted为true,所以将ACTION_CANCEL事件传递过去,从dispatchTransformedTouchEvent()中可以看到:
if(cancel||oldAction==MotionEvent.ACTION_CANCEL){ event.setAction(MotionEvent.ACTION_CANCEL); if(child==null){ handled=super.dispatchTouchEvent(event); }else{ handled=child.dispatchTouchEvent(event); } event.setAction(oldAction); returnhandled; }并将mFirstTouchTarget最终赋值为next,而此时mFirstTouchTarget位于TouchTarget链表尾部,所以mFirstTouchTarget会赋值为null,那么接下来的事件将不会进入到onInterceptTouchEvent()中。也就会直接交由该view处理。
如果我们没有进行事件的拦截,而是交由子view去处理,由于ViewGroup的onInterceptTouchEvent()默认并不会拦截除了ACTION_DOWN以外的事件,所以后续事件将继续交由子view去处理,如果存在子view且事件位于子view内部区域的话。
所以无论是否进行拦截,事件流都会交由view的dispatchTouchEvent()中进行处理,我们接下来跟踪一下view中的dispatchTouchEvent()处理过程:
if(actionMasked==MotionEvent.ACTION_DOWN){ //Defensivecleanupfornewgesture stopNestedScroll(); } if(onFilterTouchEventForSecurity(event)){ if((mViewFlags&ENABLED_MASK)==ENABLED&&handleScrollBarDragging(event)){ result=true; } //noinspectionSimplifiableIfStatement ListenerInfoli=mListenerInfo; if(li!=null&&li.mOnTouchListener!=null &&(mViewFlags&ENABLED_MASK)==ENABLED &&li.mOnTouchListener.onTouch(this,event)){ result=true; } if(!result&&onTouchEvent(event)){ result=true; } }当被按下时,即ACTION_DOWN时,view会停止内部的滚动,如果view没有被覆盖或遮挡时,首先会进行mListenerInfo是否为空的判断,我们看下mListenerInfo是在哪里初始化的:
ListenerInfogetListenerInfo(){ if(mListenerInfo!=null){ returnmListenerInfo; } mListenerInfo=newListenerInfo(); returnmListenerInfo; }这里可以看出,mListenerInfo一般不会是null,知道在我们使用它时调用过这段代码,而当view被加入window中的时候,会调用下面这段代码,从注释中也可以看出来:
/** *Addalistenerforattachstatechanges. * *Thislistenerwillbecalledwheneverthisviewisattachedordetached *fromawindow.Removethelistenerusing *{@link#removeOnAttachStateChangeListener(OnAttachStateChangeListener)}. * *@paramlistenerListenertoattach *@see#removeOnAttachStateChangeListener(OnAttachStateChangeListener) */ publicvoidaddOnAttachStateChangeListener(OnAttachStateChangeListenerlistener){ ListenerInfoli=getListenerInfo(); if(li.mOnAttachStateChangeListeners==null){ li.mOnAttachStateChangeListeners =newCopyOnWriteArrayList(); } li.mOnAttachStateChangeListeners.add(listener); } 到这里我们就知道,mListenerInfo一开始就是被初始化好了的,所以li不可能为null,li.mOnTouchListener!=null即当设置了TouchListener时不为null,并且view是enabled状态,一般情况view都是enable的。这个时候会调用到onTouch()事件,当onTouch()返回true时,这个时候result会赋值true。而当result为true时,onTouchEvent()将不会被调用。
从这里可以看出,onTouch()会优先onTouchEvent()调用;
当view设置touch监听并返回true时,那么它的onTouchEvent()将被屏蔽。否则会调用onTouchEvent()处理。那么让我们继续来看看onTouchEvent()中的事件处理:
if((viewFlags&ENABLED_MASK)==DISABLED){ if(action==MotionEvent.ACTION_UP&&(mPrivateFlags&PFLAG_PRESSED)!=0){ setPressed(false); } //Adisabledviewthatisclickablestillconsumesthetouch //events,itjustdoesn'trespondtothem. return(((viewFlags&CLICKABLE)==CLICKABLE ||(viewFlags&LONG_CLICKABLE)==LONG_CLICKABLE) ||(viewFlags&CONTEXT_CLICKABLE)==CONTEXT_CLICKABLE); }首先,当view状态是DISABLED时,只要view是CLICKABLE或LONG_CLICKABLE或CONTEXT_CLICKABLE,都会返回true,而button默认是CLICKABLE的,textview默认不是CLICKABLE的,而view一般默认都不是LONG_CLICKABLE的。
我们继续向下看:
if(mTouchDelegate!=null){ if(mTouchDelegate.onTouchEvent(event)){ returntrue; } }如果有代理事件,仍然会返回true.
if(((viewFlags&CLICKABLE)==CLICKABLE|| (viewFlags&LONG_CLICKABLE)==LONG_CLICKABLE)|| (viewFlags&CONTEXT_CLICKABLE)==CONTEXT_CLICKABLE){ switch(action){ caseMotionEvent.ACTION_UP: booleanprepressed=(mPrivateFlags&PFLAG_PREPRESSED)!=0; if((mPrivateFlags&PFLAG_PRESSED)!=0||prepressed){ //takefocusifwedon'thaveitalreadyandweshouldin //touchmode. booleanfocusTaken=false; if(isFocusable()&&isFocusableInTouchMode()&&!isFocused()){ focusTaken=requestFocus(); } if(prepressed){ //Thebuttonisbeingreleasedbeforeweactually //showeditaspressed.Makeitshowthepressed //statenow(beforeschedulingtheclick)toensure //theuserseesit. setPressed(true,x,y); } if(!mHasPerformedLongPress&&!mIgnoreNextUpEvent){ //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){ postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); }elseif(!post(mUnsetPressedState)){ //Ifthepostfailed,unpressrightnow mUnsetPressedState.run(); } removeTapCallback(); } mIgnoreNextUpEvent=false; break; caseMotionEvent.ACTION_DOWN: mHasPerformedLongPress=false; if(performButtonActionOnTouchDown(event)){ break; } //Walkupthehierarchytodetermineifwe'reinsideascrollingcontainer. booleanisInScrollingContainer=isInScrollingContainer(); //Forviewsinsideascrollingcontainer,delaythepressedfeedbackfor //ashortperiodincasethisisascroll. if(isInScrollingContainer){ mPrivateFlags|=PFLAG_PREPRESSED; if(mPendingCheckForTap==null){ mPendingCheckForTap=newCheckForTap(); } mPendingCheckForTap.x=event.getX(); mPendingCheckForTap.y=event.getY(); postDelayed(mPendingCheckForTap,ViewConfiguration.getTapTimeout()); }else{ //Notinsideascrollingcontainer,soshowthefeedbackrightaway setPressed(true,x,y); checkForLongClick(0,x,y); } break; caseMotionEvent.ACTION_CANCEL: setPressed(false); removeTapCallback(); removeLongPressCallback(); mInContextButtonPress=false; mHasPerformedLongPress=false; mIgnoreNextUpEvent=false; break; caseMotionEvent.ACTION_MOVE: drawableHotspotChanged(x,y); //Belenientaboutmovingoutsideofbuttons if(!pointInView(x,y,mTouchSlop)){ //Outsidebutton removeTapCallback(); if((mPrivateFlags&PFLAG_PRESSED)!=0){ //Removeanyfuturelongpress/tapchecks removeLongPressCallback(); setPressed(false); } } break; } returntrue; }当view是CLICKABLE或LONG_CLICKABLE或CONTEXT_CLICKABLE状态时,当手指抬起时,如果设置了click监听,最终会调用到performClick(),触发click()事件。这点从performClick()方法中可以看出:
publicbooleanperformClick(){ finalbooleanresult; finalListenerInfoli=mListenerInfo; if(li!=null&&li.mOnClickListener!=null){ playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this); result=true; }else{ result=false; } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); returnresult; }从这里我们也可以得出,click事件会在onTouchEvent()中被调用,如果view设置了onTouch()监听并返回true,那么click事件也会被屏蔽掉,不过我们可以在onTouch()中通过调用view的performClick()继续执行click()事件,这个就看我们的业务中的需求了。
从这里我们可以看出,如果事件没有被当前view或子view处理,即返回false,那么事件就会交由外层view继续处理,直到被消费。
如果事件一直没有被处理,会最终传递到Activity的onTouchEvent()中。
到这里我们总结一下:
事件是从Activity->Window->View(ViewGroup)的一个传递流程;
如果事件没有被中途拦截,那么它会一直传到最内层的view控件;
如果事件被某一层拦截,那么事件将不会向下传递,交由该view处理。如果该view消费了事件,那么接下来的事件也会交由该view处理;如果该view没有消费该事件,那么事件会交由外层view处理,...并最终调用到activity的onTouchEvent()中,除非某一层消费了该事件;
一个事件只能交由一个view处理;
DispatchTouchEvent()总是会被调用,而且最先被调用,onInterceptTouchEvent()和onTouchEvent()在DispatchTouchEvent()内部调用;
子view不能干扰ViewGroup对ACTION_DOWN事件的处理;
子view可以通过requestDisallowInterceptTouchEvent(true)控制父view不对事件进行拦截,跳过onInterceptTouchEvent()方法的执行。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。