一、 Android分发机制概述:

Android如此受欢迎,就在于其优秀的交互性,这其中,Android优秀的事件分发机制功不可没。那么,作为一个优秀的程序员,要想做一个具有良好交互性的应用,必须透彻理解Android的事件分发机制。

要想充分理解android的分发机制,需要先对以下几个知识点有所了解:

ViewViewGroup什么?

事件

View 事件的分发机制

ViewGroup事件的分发机制

下面,就让我们沿着大致方针,开始事件分发的探究之旅吧……

二、 ViewViewGroup

AndroidUI界面都是由ViewViewGroup及其派生类组合而成的。其中,View是所有UI组件的基类,而ViewGroup是容纳这些组件的容器,其本身也是从View派生出来的,也就是说ViewGroup的父类就是View

通常来说,ButtonImageViewTextView等控件都是继承父类View来实现的。RelativeLayoutLinearLayoutFrameLayout等布局都是继承父类ViewGroup来实现的。

三、 事件:

当手指触摸到ViewViewGroup派生的控件后,将会触发一系列的触发响应事件,如:

onTouchEventonClickonLongClick等。每个View都有自己处理事件的回调方法,开发人员只需要重写这些回调方法,就可以实现需要的响应事件。

而事件通常重要的有如下三种:

MotionEvent.ACTION_DOWN 按下View,是所有事件的开始

MotionEvent.ACTION_MOVE 滑动事件

MotionEvent.ACTION_UP down对应,表示抬起

事件的响应原理:

android开发设计模式中,最广泛应用的就是监听、回调,进而形成了事件响应的过程。

ButtonOnClick为例,因为Button也是一个View,所以它也拥有View父类的方法,View中源码如下:

/**定义接口成员变量*/protectedOnClickListenermOnClickListener;/***Interfacedefinitionforacallbacktobeinvokedwhenaviewisclicked.*/publicinterfaceOnClickListener{/***Calledwhenaviewhasbeenclicked.**@paramvTheviewthatwasclicked.*/voidonClick(Viewv);}/***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;}

/**触摸了屏幕后,实现并调用的方法*/

publicbooleanonTouchEvent(MotionEventevent){…..if(mPerformClick==null){mPerformClick=newPerformClick();}if(!post(mPerformClick)){performClick();}…..}

以上是View源码中关键代码行,以Button为例,假设需要在一个布局上添加一个按钮,并实现它的OnClick事件,需要如下步骤:

1、 OnClickListener类是一个当控件被点击后进行回调的一个接口,它完成被点击后的回调通知。

2、 创建一个按钮Button,并设置监听事件,对这个Button进行setOnClickListener操作

3、 当手指触摸到Button按钮,通过一系列方法(之后将会详细讲解,这里暂时忽略),触发并执行到onTouchEvent方法并执行mPerformClick方法,在mPerformClick方法中,首先会判断注册的mOnClickListener是否为空,若不为空,它就会回调之前注册的onClick方法,进而执行用户自定义代码。

事件响应机制,简单来说上面的例子就已经基本上诠释了

注册一个监听对象

实现监听对象的监听事件

当某一触发事件到来,在触发事件中通过注册过的监听对象,回调注册对象的响应事件,来完成用户自定义实现。

但凡明白了这一个简单的事件响应的过程,就离事件驱动开发整个过程就不远了,大道至简,请完全理解了这个例子,再继续之后的学习,事半功倍。

  1. 四、 View事件的分发机制:

通过上面的例子,我们初步的接触了View的事件分发机制,再进一步了解。首先,我们要熟悉dispatchTouchEventonTouchEvent两个函数,这两个函数都是View的函数,要理解View事件的分发机制,只要清楚这两个函数就基本上清楚了。

在这里先提醒一句,这里的“分发”是指一个触摸或点击的事件发生,分发给当前触摸控件所监听的事件(如OnClickonTouch等),进而来决定是控件的哪个函数来响应此次事件。

dispatchTouchEvent:

此函数负责事件的分发,你只需要记住当触摸一个View控件,首先会调用这个函数就行,在这个函数体里决定将事件分发给谁来处理。

onTouchEvent:

此函数负责执行事件的处理,负责处理事件,主要处理MotionEvent.ACTION_DOWN

MotionEvent.ACTION_MOVE

MotionEvent.ACTION_UP这三个事件。

public boolean onTouchEvent (MotionEvent event)

参数event为手机屏幕触摸事件封装类的对象,其中封装了该事件的所有信息,例如触摸的位置、触摸的类型以及触摸的时间等。该对象会在用户触摸手机屏幕时被创建。

那么它是如何执行这个流程的呢?我们还以布局上的按钮为例,看看它是如何实现的。(看图①)

图①

我们知道,View做为所有控件的父类,它本身定义了很多接口来监听触摸在View上的事件,如OnClickListener(点击)、OnLongClickListener(长按)、OnTouchListener(触摸监听)等,那么当手指触摸到View时候,该响应“点击”还是触摸,就是根据dispatchTouchEventonTouchEvent这两个函数组合实现的,我们之下的讨论,仅对常用的“点击OnClick”和“触摸onTouch”来讨论,顺藤摸瓜,找出主线,进而搞清楚View的事件分发机制。

对于上面的按钮,点击它一下,我们期望2种结果,第一种:它响应一个点击事件。第二种:不响应点击事件。

第一种源码:

publicclassMainActivityextendsActivityimplementsOnClickListener,OnTouchListener{privateButtonbtnButton;@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);btnButton=(Button)findViewById(R.id.btn);btnButton.setOnClickListener(this);btnButton.setOnTouchListener(this);}@OverridepublicvoidonClick(Viewv){//TODOAuto-generatedmethodstubswitch(v.getId()){caseR.id.btn:Log.e("View","onClick===========>");break;default:break;}}@OverridepublicbooleanonTouch(Viewv,MotionEventevent){//TODOAuto-generatedmethodstubLog.e("View","onTouch..................................");returnfalse;}}

(图②)

第二种源码:

publicclassMainActivityextendsActivityimplementsOnClickListener,OnTouchListener{privateButtonbtnButton;@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);btnButton=(Button)findViewById(R.id.btn);btnButton.setOnClickListener(this);btnButton.setOnTouchListener(this);}@OverridepublicvoidonClick(Viewv){//TODOAuto-generatedmethodstubswitch(v.getId()){caseR.id.btn:Log.e("View","onClick===========>");break;default:break;}}@OverridepublicbooleanonTouch(Viewv,MotionEventevent){//TODOAuto-generatedmethodstubLog.e("View","onTouch..................................");returntrue;}}

(图③)

结果分析:

上面两处代码,第一种执行了OnClick函数和OnTouch函数,第二种执行了OnTouch函数,并没有执行OnClick函数,而且对两处代码进行比较,发现只有在onTouch处返回值truefalse不同。当onTouch返回falseonClick被执行了,返回trueonClick未被执行。

为什么会这样呢?我们只有深入源码才能分析出来。

前面提到,触摸一个View就会执行dispatchTouchEvent方法去“分发”事件, 既然触摸的是按钮Button,那么我们就查看Button的源码,寻找dispatchTouchEvent方法,Button源码中没有dispatchTouchEvent方法,但知道Button继承自TextView,寻找TextView,发现它也没有dispatchTouchEvent方法,继续查找TextView的父类View,发现ViewdispatchTouchEvent方法,那我们就分析dispatchTouchEvent方法。

主要代码如下:

publicbooleandispatchTouchEvent(MotionEventevent){if(onFilterTouchEventForSecurity(event)){//noinspectionSimplifiableIfStatementif(mOnTouchListener!=null&&(mViewFlags&ENABLED_MASK)==ENABLED&&mOnTouchListener.onTouch(this,event)){returntrue;}if(onTouchEvent(event)){returntrue;}}returnfalse;}

分析:

先来看dispatchTouchEvent函数返回值,如果返回true,表明事件被处理了,反之,表明事件未被处理。

if (onFilterTouchEventForSecurity(event))这个是事件安全过滤,与主题无关,继续看。

if(mOnTouchListener!=null&&(mViewFlags&ENABLED_MASK)==ENABLED&&mOnTouchListener.onTouch(this,event)){returntrue;}

这个判定很重要,mOnTouchListener != null,判断该控件是否注册了OnTouchListener对象的监听,(mViewFlags & ENABLED_MASK) == ENABLED,判断当前的控件是否能被点击(比如Button默认可以点击,ImageView默认不许点击,看到这里就了然了),mOnTouchListener.onTouch(this, event)这个是关键,这个调用,就是回调你注册在这个View上的mOnTouchListener对象的onTouch方法,如果你在onTouch方法里返回false,那么这个判断语句就跳出,去执行下面的程序,否则,当前2个都返回了true,自定义onTouch方法也返回true,条件成立,就直接返回了,不再执行下面的程序。接下来,if (onTouchEvent(event)) 这个判断很重要,能否回调OnClickListener接口的onClick函数,关键在于此,可以肯定的是,如果上面if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&

mOnTouchListener.onTouch(this, event))返回true,那么就不会执行并回调OnClickListener接口的onClick函数。

接下来,我们看onTouchEvent这个函数,看它是如何响应点击事件的。

主要代码如下:

publicbooleanonTouchEvent(MotionEventevent){finalintviewFlags=mViewFlags;if((viewFlags&ENABLED_MASK)==DISABLED){if(event.getAction()==MotionEvent.ACTION_UP&&(mPrivateFlags&PRESSED)!=0){mPrivateFlags&=~PRESSED;refreshDrawableState();}//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(prepressed){//Thebuttonisbeingreleasedbeforeweactually//showeditaspressed.Makeitshowthepressed//statenow(beforeschedulingtheclick)toensure//theuserseesit.mPrivateFlags|=PRESSED;refreshDrawableState();}if(!mHasPerformedLongPress){//Thisisatap,soremovethelongpresscheckremoveLongPressCallback();//Onlyperformtakeclickactionsifwewereinthepressedstateif(!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,unpressrightnowmUnsetPressedState.run();}removeTapCallback();}break;caseMotionEvent.ACTION_DOWN:mHasPerformedLongPress=false;if(performButtonActionOnTouchDown(event)){break;}//Walkupthehierarchytodetermineifwe'reinsideascrollingcontainer.booleanisInScrollingContainer=isInScrollingContainer();//Forviewsinsideascrollingcontainer,delaythepressedfeedbackfor//ashortperiodincasethisisascroll.if(isInScrollingContainer){mPrivateFlags|=PREPRESSED;if(mPendingCheckForTap==null){mPendingCheckForTap=newCheckForTap();}postDelayed(mPendingCheckForTap,ViewConfiguration.getTapTimeout());}else{//Notinsideascrollingcontainer,soshowthefeedbackrightawaymPrivateFlags|=PRESSED;refreshDrawableState();checkForLongClick(0);}break;caseMotionEvent.ACTION_CANCEL:mPrivateFlags&=~PRESSED;refreshDrawableState();removeTapCallback();break;caseMotionEvent.ACTION_MOVE:finalintx=(int)event.getX();finalinty=(int)event.getY();//Belenientaboutmovingoutsideofbuttonsif(!pointInView(x,y,mTouchSlop)){//OutsidebuttonremoveTapCallback();if((mPrivateFlags&PRESSED)!=0){//Removeanyfuturelongpress/tapchecksremoveLongPressCallback();//NeedtoswitchfrompressedtonotpressedmPrivateFlags&=~PRESSED;refreshDrawableState();}}break;}returntrue;}returnfalse;}publicbooleanperformClick(){sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);if(mOnClickListener!=null){playSoundEffect(SoundEffectConstants.CLICK);mOnClickListener.onClick(this);returntrue;}returnfalse;}

代码量太大了,不过不要紧,我们通过主要代码分析一下。

publicbooleanonTouchEvent(MotionEventevent){//控件不能被点击if((viewFlags&ENABLED_MASK)==DISABLED){…}//委托代理别的View去实现if(mTouchDelegate!=null){if(mTouchDelegate.onTouchEvent(event)){returntrue;}}//控件能够点击或者长按if(((viewFlags&CLICKABLE)==CLICKABLE||(viewFlags&LONG_CLICKABLE)==LONG_CLICKABLE)){switch(event.getAction()){//抬起事件caseMotionEvent.ACTION_UP:…...if(!focusTaken){//UseaRunnableandpostthisratherthancalling//performClickdirectly.Thisletsothervisualstate//oftheviewupdatebeforeclickactionsstart.if(mPerformClick==null){mPerformClick=newPerformClick();}if(!post(mPerformClick)){//这里就是去执行回调注册的onClick函数,实现点击performClick();}}……break;//按下事件caseMotionEvent.ACTION_DOWN:……break;……//移动事件caseMotionEvent.ACTION_MOVE:……break;}returntrue;}returnfalse;}


从上面主要代码可以看出onTouchEvent传参MotionEvent类型,它封装了触摸的活动事件,其中就有MotionEvent.ACTION_DOWNMotionEvent.ACTION_MOVEMotionEvent.ACTION_UP三个事件。我们在来看看onTouchEvent的返回值,因为onTouchEvent是在dispatchTouchEvent事件分发处理中调用的,

publicbooleandispatchTouchEvent(MotionEventevent){……if(onTouchEvent(event)){returntrue;}returnfasle;}

如果onTouchEvent返回truedispatchTouchEvent就返回true,表明事件被处理了,反之,事件未被处理。

程序的关键在 if (((viewFlags & CLICKABLE) == CLICKABLE ||

(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE))的判断里,我们发现无论switch的分支在什么地方跳出,返回都是true。这就表明,无论是三个事件中的哪一个,都会返回true

参照下图,结合上述,不难理解View的分发机制了。

(图④)

  1. 五、 ViewGroup事件分发机制:

ViewGroup事件分发机制较View的稍微复杂一些,不过对View的机制只要精确的理解后,仔细看过这一节,睡几觉起来,估计也就悟出来了,学习就是这么奇怪,当下理解不了或模糊的地方,只要脑子有印象,忽然一夜好像就懂了。

先来看下面的一个简单布局,我们将通过例子,了解ViewGroup+Viewandroid事件处理机制。

(图⑤)

上图由:黑色为线性布局LinearLayout,紫色为相对布局RelativeLayout,按钮Button三部分组成。RelativeLayoutLinearLayout的子布局,ButtonRelativeLayout的子布局。以下RelativeLayout简称(R),LinearLayout简称(L),Button简称(B)。

经过前面讲解,我们首先知道这样两件事情。

1、(R)和(L)的父类是ViewGroup,(B)的父类是View

2dispatchTouchEvent这个函数很重要,不论是ViewGroup还是View,都由它来处理事件的消费和传递。

下面,我们通过横向和纵向两个维度,通过源码和图解的方式,充分理解事件的传递机制。

先来看整体的事件传递过程:

(图⑥)

当手指点击按钮B时,事件传递的顺序是从底向上传递的,也就是按照L->R->B的顺序由下往上逐层传递,响应正好相反,是自上而下。

L首先接收到点击事件,L的父类是ViewGroup类,并将事件传递给dispatchTouchEvent方法,dispatchTouchEvent函数中判断该控件L是否重载了onInterceptTouchEvent方法进行事件拦截,onInterceptTouchEvent默认返回false不拦截,那么dispatchTouchEvent方法将事件传递给R去处理(进入第2流程处理),如果返回true表示当前L控件拦截了事件向其它控件的传递,交给它自己父类ViewdispatchTouchEvent去处理,在父方法的dispatchTouchEvent中,将会按照前面讲的View的事件处理机制去判断,比如判断L是否重载了onTouch方法,是否可点击,是否做了监听等事件。

R也是ViewGroup的子类,因此与第1流程基本相似,如果onInterceptTouchEvent返回了false,表示事件将不拦截继续传递给B

BView的子类,它没有onInterceptTouchEvent方法,直接交给自己父类ViewdispatchTouchEvent去处理,流程同不再敷述。

总结:

onInterceptTouchEvent只有ViewGroup才有,当一个控件是继承自ViewGroup而来的,那么它就可能会有子控件,因此,才有可能传递给子控件,而继承自View的控件,不会有子控件,也就没有onInterceptTouchEvent函数了。

通过dispatchTouchEvent分发的控件返回值Truefalse,表示当前控件是否消费了传递过来的事件,如果消费了,返回True,反之false。消费了,就不再继续传递了,没有消费,如果有子控件将继续传递。

啰嗦点,如果想再深层次了解一下,再次从源码ViewGroup来分析一个L控件的事件传递过程,请看下图:

(图⑦)

结合上面的图例,下面列出ViewGroup源码来分析一下,我们只需要分析ViewGroupdispatchTouchEventonInterceptTouchEventdispatchTransformedTouchEvent三个方法即可。

publicbooleandispatchTouchEvent(MotionEventev){if(mInputEventConsistencyVerifier!=null){mInputEventConsistencyVerifier.onTouchEvent(ev,1);}booleanhandled=false;if(onFilterTouchEventForSecurity(ev)){finalintaction=ev.getAction();finalintactionMasked=action&MotionEvent.ACTION_MASK;//Handleaninitialdown.if(actionMasked==MotionEvent.ACTION_DOWN){//Throwawayallpreviousstatewhenstartinganewtouchgesture.//Theframeworkmayhavedroppedtheuporcanceleventforthepreviousgesture//duetoanappswitch,ANR,orsomeotherstatechange.cancelAndClearTouchTargets(ev);resetTouchState();}//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;}//Checkforcancelation.finalbooleancanceled=resetCancelNextUpFlag(this)||actionMasked==MotionEvent.ACTION_CANCEL;//Updatelistoftouchtargetsforpointerdown,ifneeded.finalbooleansplit=(mGroupFlags&FLAG_SPLIT_MOTION_EVENTS)!=0;TouchTargetnewTouchTarget=null;booleanalreadyDispatchedToNewTouchTarget=false;if(!canceled&&!intercepted){if(actionMasked==MotionEvent.ACTION_DOWN||(split&&actionMasked==MotionEvent.ACTION_POINTER_DOWN)||actionMasked==MotionEvent.ACTION_HOVER_MOVE){finalintactionIndex=ev.getActionIndex();//always0fordownfinalintidBitsToAssign=split?1<<ev.getPointerId(actionIndex):TouchTarget.ALL_POINTER_IDS;//Cleanupearliertouchtargetsforthispointeridincasethey//havebecomeoutofsync.removePointersFromTouchTargets(idBitsToAssign);finalintchildrenCount=mChildrenCount;if(childrenCount!=0){//Findachildthatcanreceivetheevent.//Scanchildrenfromfronttoback.finalView[]children=mChildren;finalfloatx=ev.getX(actionIndex);finalfloaty=ev.getY(actionIndex);for(inti=childrenCount-1;i>=0;i--){finalViewchild=children[i];if(!canViewReceivePointerEvents(child)||!isTransformedTouchPointInView(x,y,child,null)){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();mLastTouchDownIndex=i;mLastTouchDownX=ev.getX();mLastTouchDownY=ev.getY();newTouchTarget=addTouchTarget(child,idBitsToAssign);alreadyDispatchedToNewTouchTarget=true;break;}}}if(newTouchTarget==null&&mFirstTouchTarget!=null){//Didnotfindachildtoreceivetheevent.//Assignthepointertotheleastrecentlyaddedtarget.newTouchTarget=mFirstTouchTarget;while(newTouchTarget.next!=null){newTouchTarget=newTouchTarget.next;}newTouchTarget.pointerIdBits|=idBitsToAssign;}}}//Dispatchtotouchtargets.if(mFirstTouchTarget==null){//Notouchtargetssotreatthisasanordinaryview.handled=dispatchTransformedTouchEvent(ev,canceled,null,TouchTarget.ALL_POINTER_IDS);}else{//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;}}//Updatelistoftouchtargetsforpointeruporcancel,ifneeded.if(canceled||actionMasked==MotionEvent.ACTION_UP||actionMasked==MotionEvent.ACTION_HOVER_MOVE){resetTouchState();}elseif(split&&actionMasked==MotionEvent.ACTION_POINTER_UP){finalintactionIndex=ev.getActionIndex();finalintidBitsToRemove=1<<ev.getPointerId(actionIndex);removePointersFromTouchTargets(idBitsToRemove);}}if(!handled&&mInputEventConsistencyVerifier!=null){mInputEventConsistencyVerifier.onUnhandledEvent(ev,1);}returnhandled;}publicbooleanonInterceptTouchEvent(MotionEventev){returnfalse;}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;}

代码量比较大,我们先概述一下各个函数的主要作用。

dispatchTouchEvent主要用来分发事件,函数主要作用是来决定当前的事件是交由自己消费处理,还是交由子控件处理。

onInterceptTouchEvent主要来决定当前控件是否需要拦截传递给子控件,如果返回True表示该控件拦截,并交由自己父类的dispatchTouchEvent处理消费,如果返回false表示不拦截,允许传递给子控件处理。

dispatchTransformedTouchEvent主要根据传来的子控件,决定是自身处理消费,还是交由子控件处理消费。

我们主要来分析一下dispatchTouchEvent函数:

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;}


这段代码,如果当前传递的事件是Down(按下)或者当前触摸链表不为空,那么它调用onInterceptTouchEvent函数,判断是否进行事件拦截处理,通过返回值来决定intercepted变量的值。

接下来if (!canceled && !intercepted){} 这个括号内的代码需要注意了,只有当intercepted返回值为false的时候,才满足这个条件进入代码段。因此,我们结合onInterceptTouchEvent源码,发现它默认值返回的是false,也就说如果你不重载onInterceptTouchEvent方法并令其返回True,它一定是返回false,并能够执行花括号内的代码。

我们分析一下花括号中的代码,if (actionMasked == MotionEvent.ACTION_DOWN

|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)

|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {}判断当前的事件是否是ACTION_DOWNACTION_POINTER_DOWN(多点触摸)、ACTION_HOVER_MOVE(悬停),如果是,执行花括号内代码,

final int childrenCount = mChildrenCount;

if (childrenCount != 0) {}判断当前控件是否有子控件,如果大于0,执行花括号内代码,

for (int i = childrenCount - 1; i >= 0; i--)遍历子控件,

if (!canViewReceivePointerEvents(child)

判断当前的downPOINTER_DOWNHOVER_MOVE三个事件的坐标点是否落在了子控件上,如果落在子控件上,

if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))

通过dispatchTransformedTouchEvent传递事件,交由子控件判断是否传递或自己消费处理。如果dispatchTransformedTouchEvent返回true,表示子控件已消费处理,并添加此子控件View到触摸链表,并放置链表头,并结束遍历子控件。newTouchTarget = addTouchTarget(child, idBitsToAssign);false表示未处理。

接着分析

if(mFirstTouchTarget==null){handled=dispatchTransformedTouchEvent(ev,canceled,null,TouchTarget.ALL_POINTER_IDS);}else{……}

mFirstTouchTarget什么时候为空呢?从前面的代码可以看到,如果onInterceptTouchEvent返回为false(也就是不拦截),mFirstTouchTarget就为空,直接交给自己父View执行dispatchTouchEvent去了。如果mFirstTouchTarget不为空,它就取出触摸链表,逐个遍历判断处理,如果前面比如Down事件处理过了,就不再处理了。


更多相关文章

  1. Android命令Monkey压力测试,详解
  2. RenderScript 让你的Android计算速度快的飞上天!
  3. Android(安卓)控件布局常用属性
  4. Android的6种布局管理器总结
  5. android按键焦点事件分析(应用开发篇)
  6. RelativeLayout_相对布局 属性的描述
  7. Android(安卓)Phone设计介绍
  8. 相对布局各种属性说明
  9. 【Android】gravity、layout_gravity 以及 left、start的区别

随机推荐

  1. android安装app
  2. ch07 Android 回调方法
  3. Android客户端性能测试
  4. android 项目收获02
  5. android 代码创建快捷方式
  6. 【Android】Activity 生命周期详解
  7. android 多媒体框架
  8. android系统编译资料
  9. Android实现文本的展开收起
  10. android 把图变成灰色