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()方法的执行。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。