假設(shè)手機(jī)屏幕上有一個(gè)button。我們?nèi)c(diǎn)擊了一下,然后button做出了相應(yīng)的反應(yīng),那么這個(gè)過程其實(shí)這樣的,當(dāng)手機(jī)點(diǎn)擊到屏幕時(shí),TP(Touch panel)傳感器的數(shù)據(jù)發(fā)生了變化。數(shù)據(jù)經(jīng)過驅(qū)動(dòng)的處理(其實(shí)用示波器來看傳感器數(shù)據(jù),這個(gè)數(shù)據(jù)肯定不可能那么規(guī)整),然后數(shù)據(jù)依次傳遞到內(nèi)核,framwork,然后再傳遞相應(yīng)的app的當(dāng)前Activity中。我們說android中View的事件分發(fā),其實(shí)指的就是在此之后的過程。當(dāng)手指接觸屏幕所產(chǎn)生的反應(yīng),我們稱之為事件(MotionEvent
).
當(dāng)然MotionEvent
實(shí)際上也可以細(xì)分為很多種,典型的有以下幾種。
ACTION_DOWN
: 手指剛剛接觸到屏幕。ACTION_MOVE
:手指在屏幕上滑動(dòng)。ACTION_UP
:手指從屏幕上離開的一瞬間。
所以正常情況下,手指觸摸屏幕會(huì)觸發(fā)一系列事件。
比如 DOWN -> MOVE -> UP
。從手指接觸屏幕那一刻起,到手指離開屏幕那一刻結(jié)束,中間所產(chǎn)生的一系列事件總和,我們稱之為一個(gè)事件序列。它以一個(gè)down
事件開始,中間夾雜著0個(gè)或多個(gè)move
事件,以一個(gè)up
事件結(jié)束。
我們手指接觸到屏幕,怎么判斷是點(diǎn)擊還是滑動(dòng)呢?其實(shí)在手機(jī)framework中,有一個(gè)默認(rèn)值,比如8px(和具體的設(shè)備和手機(jī)廠商有關(guān)),當(dāng)手指移動(dòng)的距離大于該值時(shí),就是滑動(dòng)事件
TouchSlop
,否則就是點(diǎn)擊事件。
我們所討論的事件分發(fā),指的就是 MotionEvent
從 Activity -> Window -> ViewGroup -> View
這個(gè)過程。 (畢竟我們都是 做上層app開發(fā)的,像TP數(shù)據(jù)怎么處理之類的那些是驅(qū)動(dòng)工程師們關(guān)心的問題)。
這個(gè)事件分發(fā)的過程,總結(jié)起來其實(shí)很簡(jiǎn)單,因?yàn)樵搱?chǎng)景和我們?nèi)粘I钪械膱?chǎng)景是一致的。
boss(老板)要做某件事情,但是既然身為boss,人家肯定不用自己動(dòng)手嘛,所以這事交給下面的manager(經(jīng)理)就好了,經(jīng)理好歹也是領(lǐng)導(dǎo)嘛,所以也不用自己親自動(dòng)手,交給下面的worker(工人)就好了,所以轉(zhuǎn)了一圈,干活的還是最下面的工人,如果worker把這活處理了,那么這件事情就算完了,可是也有例外情況,如果worker發(fā)現(xiàn)這個(gè)活自己處理不了,那么這事就只能向上反饋給manager 了,如果manager能把這活給處理了,那自然最好,可是如果manager發(fā)現(xiàn)這活自己也處理不了,那就只能再反饋給boos,讓boss去處理了
事件分發(fā)也是如此。點(diǎn)擊屏幕上的一個(gè)點(diǎn),事件那么經(jīng)過activity -> window
,然后傳遞到 界面最外層(或者叫最頂級(jí))的ViewGroup
容器中(指的是Relativelayout
,LinearLayout
等)。 然后最后依次傳遞到被包裹在最里面到 View
中。(當(dāng)然,如果View
包裹的有幾層的ViewGroup
,那么肯定是依次先傳遞給它們, 并且需要注意,最里面的控件完全可以是一個(gè)ViewGroup
,而它不必包含View
。)
事件被傳遞到了最里面的View
,所以交給了View
來處理,如果View沒處理,那么就依次倒過來向上回溯。分別交給各級(jí)ViewGroup
,如果它們也沒處理,那么就繼續(xù)向上回溯,乃至Window
,Activity
中來處理。
View
就是最底層的worker,而boss,manager都是包裹在它外面的ViewGroup
,如果層級(jí)比較復(fù)雜的話,還要經(jīng)過多層才能傳到到最底層的View
,(比如經(jīng)過老板,總經(jīng)理,部門經(jīng)理,項(xiàng)目經(jīng)理等多層,事情才分配到最底層的工人這里),View
處理不了的,那么就再依次向上反饋給各級(jí)領(lǐng)導(dǎo),看看誰去處理。
該過程其實(shí)就是一個(gè)遞歸的過程。這是android系統(tǒng)中默認(rèn)的事件分發(fā)規(guī)則,如果我們理解了這個(gè)規(guī)則,那么我們就可以解決很多實(shí)際問題,比如 我們可以在某一層ViewGroup
中來攔截處理事件,而不用向下傳遞到View
中。
Notice:
因?yàn)?code style="margin: 0px; padding: 0.2em 0px; box-sizing: border-box; line-height: 1.8; vertical-align: middle; display: inline-block; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 12px; background-color: rgba(0, 0, 0, 0.04); border: none !important; border-radius: 3px;">ViewGroup extends View,所以從繼承關(guān)系上來講,
ViewGroup
也是一個(gè)View,而在本文當(dāng)中說的ViewGroup
指的是RelativeLayout
,LinearLayout
等容器類View,而View指的是Button
,TextView
等不會(huì)再有child的View(因?yàn)樗鼈儾粫?huì)再包裹什么了)。之所以要明確區(qū)分,是因?yàn)樗鼈兊挠行┓椒ǖ膶?shí)現(xiàn)是有區(qū)別的。為了方便,在本文當(dāng)中,稱呼
控件
指的是View或ViewGroup(也可能是View和ViewGroup)。在本文當(dāng)中,一個(gè)控件處理了事件,并return了對(duì)應(yīng)的boolean值,我們稱之為 事件被 消費(fèi)了。(如果沒有return值,我們?cè)趺粗肋@個(gè)事件是否被處理了呢?)
既然是事件分發(fā),那么不得不提到到幾個(gè)主要方法。
View
中:
//從名字中就可以看出來,是負(fù)責(zé)分發(fā)事件的方法。 public boolean dispatchTouchEvent(MotionEvent event) //該方法中設(shè)置去消費(fèi)事件,比如我們平時(shí)常用的OnClickView,OnLongClick等監(jiān)聽事件都是在這里被調(diào)用的。 public boolean onTouchEvent(MotionEvent event)
實(shí)際上,View當(dāng)中也有具體負(fù)責(zé)消費(fèi)事件的接口。
public interface OnTouchListener { boolean onTouch(View v, MotionEvent event); } // 這個(gè)就是我們最常用的 點(diǎn)擊事件方法。 public interface OnClickListener { void onClick(View v); }
說完了View
,那么再來看看ViewGroup
中的主要方法。
//View中也有該方法,但是和ViewGroup中的實(shí)現(xiàn)方式不同 public boolean dispatchTouchEvent(MotionEvent ev) //該方法是ViewGroup特有的,決定是否攔截事件。(默認(rèn)返回false),如果返回true,則事件不會(huì)再向child傳遞了,這個(gè)道理很簡(jiǎn)單,ViewGroup是屬于領(lǐng)導(dǎo), 它才有權(quán)利決定是否攔截事件。而View是最底層的工人,是沒有決定權(quán)的。 public boolean onInterceptTouchEvent(MotionEvent ev) //幾個(gè)相關(guān)方法或接口中,ViewGroup實(shí)現(xiàn)了這兩個(gè),其他都沒重寫,也就是利用View當(dāng)中的實(shí)現(xiàn)
和事件分發(fā)有關(guān)的方法就這幾個(gè)?;镜牡览砥鋵?shí)也不復(fù)雜。(聯(lián)想老板給工人分配任務(wù)的場(chǎng)景就可以了)。
而關(guān)于這個(gè)過程,可以使用偽代碼描述如下:
對(duì)于View
:
//@author www.yaoxiaowen.comboolean dispatchTouchEvent(MotionEvent event){ boolean result = false; if (OnTouchListener.OnTouch(view, event)){ result = true; } if (!result && onTouchEvent(event)){ result = true; } return result; }//實(shí)際上這個(gè)類,處理的比較復(fù)雜,比如分為 ACTION_DOWN, ACTION_UP 之類的,//不過這里是僅僅是作為示例的偽代碼。//@author www.yaoxiaowen.comboolean onTouchEvent(MotionEvent event){ if (clickable || clickable){ if (event.getAction()==ACTION_UP && OnClickListener!=null){ OnClickListener.onClick(); } return true; } return false; }
而對(duì)于 ViewGroup
,偽代碼如下:
//@author www.yaoxiaowen.comboolean dispatchTouchEvent(MotionEvent event){ boolean result = false; if (onInterceptTouchEvent(event)){ //Notice:這個(gè)地方不是調(diào)用 的this.onTouchEvent。調(diào)用自己那就是死循環(huán)了,既然調(diào)用了父類View的dispatchTouchEvent(),想想View的該方法做什么,所以實(shí)際上就是把事件分發(fā)給自己了。 //(因?yàn)轭愃苚nTouchEvent, onTouchListener方法在ViewGroup當(dāng)中沒實(shí)現(xiàn),只在父類View當(dāng)中才實(shí)現(xiàn)) reuslt = super.dispatchTouchEvent(); }else{ //child可能是 ViewGroup,也可能是 View result = child.dispatchTouchEvent(event); } return result; }
結(jié)合偽代碼,再想象具體的分發(fā)流程。仔細(xì)琢磨一下,就可以大概的了解這個(gè)分發(fā)流程了。
雖然總結(jié)的內(nèi)容的不復(fù)雜,但是實(shí)際上依舊有很多的細(xì)節(jié)。下面我們就結(jié)合具體的源碼來進(jìn)行分析。(具體的源碼非常復(fù)雜,參考了不少博客,然后自己也讀了幾遍,但是也只是理解了一小部分,畢竟自己也水平有限。不過我們要分析的也就是流程的主干內(nèi)容。)
沿著事件分發(fā)的流程。Activity -> Window -> ViewGroup -> View
.
先看Activity
:
//Activity.javapublic boolean dispatchTouchEvent(MotionEvent ev) { //省略部分代碼 //從這句代碼中,將事件分分發(fā)到了 Window if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); } public boolean onTouchEvent(MotionEvent event) { if (mWindow.shouldCloseOnTouch(this, event)) { finish(); return true; } return false; }
在Activity中,事件被傳遞給了Window,但是此時(shí)要注意了,如果Window當(dāng)中未消費(fèi)事件。(就是返回了false)。那么此時(shí)就調(diào)用Activity自身的onToucheEvent()了。這在后面的流程中,原理是相同的。就是根據(jù)返回值來判斷是否消費(fèi)了事件,整個(gè)事件分發(fā)機(jī)制從上向下,再?gòu)南碌缴线@套遞歸的回溯機(jī)制,就是如此。
Activity當(dāng)中把事件分發(fā)給了Window,不過Window類是abstract
的,Window的唯一實(shí)現(xiàn) 就是PhoneWindow
,然后這中間的傳遞過程就比較復(fù)雜(因?yàn)橹虚g很多源碼其實(shí)我也沒看懂),反正最后就傳遞到了Activity布局中最外層的ViewGroup中。
ViewGroup中的dispatchTouchEvent
方法的實(shí)現(xiàn)就比較長(zhǎng)了,所以我們這里就先從上到下分片段的來看這個(gè)方法的具體實(shí)現(xiàn)。
//ViewGroup.java當(dāng)中的代碼@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) { //首先,在該方法的開頭,并沒有 super()的代碼,這說明 dispatchTouchEvent方法就是在ViewGroup當(dāng)中實(shí)現(xiàn)的,View和ViewGroup當(dāng)中各有一套實(shí)現(xiàn)規(guī)則。 //...... boolean handled = false; //...... // Check for interception. final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { //Notice:注意 這個(gè)地方調(diào)用了 onInterceptTouchEvent 方法來判斷在該ViewGroup當(dāng)中是否攔截。 // onInterceptTouchEvent 默認(rèn)的返回值 是false。 intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { intercepted = false; } } else { // There are no touch targets and this action is not an initial down // so this view group continues to intercept touches. intercepted = true; } // If intercepted, start normal event dispatch. Also if there is already // a view that is handling the gesture, do normal event dispatch. if (intercepted || mFirstTouchTarget != null) { ev.setTargetAccessibilityFocus(false); }
而如果在ViewGroup中不攔截該事件,或者該事件不該在本ViewGroup當(dāng)中處理,那么就會(huì)遍歷它的child進(jìn)行處理。
//ViewGroup.java 的 dispatchTouchEvent(MotionEvent event) 方法 // ...... final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); // Find a child that can receive the event. // Scan children from front to back. final ArrayList<View> preorderedList = buildTouchDispatchChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex); // If there is a view that has accessibility focus we want it // to get the event first and if not handled we will perform a // normal dispatch. We may do a double iteration but this is // safer given the timeframe. if (childWithAccessibilityFocus != null) { if (childWithAccessibilityFocus != child) { continue; } childWithAccessibilityFocus = null; i = childrenCount - 1; } //...... resetCancelNextUpFlag(child); //注意這一句調(diào)用,將事件往下進(jìn)行分發(fā)。 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // Child wants to receive touch within its bounds. mLastTouchDownTime = ev.getDownTime(); if (preorderedList != null) { // childIndex points into presorted list, find original index for (int j = 0; j < childrenCount; j++) { if (children[childIndex] == mChildren[j]) { mLastTouchDownIndex = j; break; } } } else { mLastTouchDownIndex = childIndex; } mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); http://www.cnblogs.com/yaoxiaowen/p/6925702.html