View的事件分發(fā)機(jī)制

1.View點(diǎn)擊事件的傳遞規(guī)則

首先,用戶觸摸屏幕的時(shí)候系統(tǒng)必須對事件做出相應(yīng)反應(yīng).而這個(gè)事件就是產(chǎn)生一個(gè)MotionEvent然后按照一定的規(guī)則傳遞給每一個(gè)View去進(jìn)行相應(yīng)的處理.這就是我們所謂的事件分發(fā)了.點(diǎn)擊事件的分發(fā)主要設(shè)計(jì)一下幾個(gè)主要的方法:

用來進(jìn)行事件的分發(fā).如果有事件傳遞給當(dāng)前的View,那么該View一定會(huì)去調(diào)用這個(gè)方法

返回值受當(dāng)前View的onTouchEvent和下級View的dispatchTouchEvent的影響

返回值表示當(dāng)前的事件時(shí)候已經(jīng)被處理完成

public boolean dispatchTouchEvent(MotionEvent e)

這個(gè)方法在上面那個(gè)方法中內(nèi)部調(diào)用,用來判斷是否攔截這個(gè)事件

如果當(dāng)前View攔截了某個(gè)事件 那么在同一個(gè)事件序列中就不會(huì)再次被調(diào)用

返回值表示是否攔截當(dāng)前事件

public boolean onInterceptTouchEvent(MotionEvent e)

也是在第一個(gè)方法中去調(diào)用 用來處理攔截下來的事件

返回值為真表示該事件已經(jīng)被處理 否則 沒有處理

在同一事件序列中View無法再次接收到事件

public boolean onTouchEvent(MotionEvent e)

可以用一段偽代碼來表示一下三者的關(guān)系:

public boolean dispatchTouchEvent(MotionEvent e) {

 boolean consume = false;

 if (onInterceptTouchEvent(e)) {

 consume = onTouchEvent(e);

 } else {

 cnsume = childView.dispathcTouchEvent(e);

}

return cosume;

} 

從上面的代碼中我們基本可以總結(jié)出這樣的結(jié)論:
對于一個(gè)根ViewGroup來說,當(dāng)接收到一個(gè)MotionEvent的時(shí)候:
調(diào)用dispatchTouchEvent方法調(diào)用onInterceptTouchEvent方法返回值為true,則表示攔截當(dāng)前事件,調(diào)用onTouchEvent來處理這個(gè)事件
返回值為false,則當(dāng)前事件將會(huì)被傳遞給childView,childView繼續(xù)調(diào)用dispatchTouchEvent方法

如此往復(fù),直至事件被處理.
當(dāng)一個(gè)View需要處理一個(gè)事件的時(shí)候
如果該View設(shè)置了onTouchListener,則會(huì)調(diào)用onTouch方法如果onTouch方法返回false,則去調(diào)用onTouchEvent如果設(shè)置了onClickListener,那么在onTouchEvent方法中將會(huì)調(diào)用onClick方法

反之,onTouchEvent則不會(huì)被調(diào)用

當(dāng)一個(gè)事件產(chǎn)生的時(shí)候,它的傳遞過程遵循這樣的過程:Activity->Windows->View;事件總是先傳遞給Activity,Activity在傳遞給Windows,Windows在傳遞給View;如果View將事件處理了,則該事件相應(yīng)就結(jié)束了.否則,事件將一級一級的繼續(xù)返回,最終會(huì)傳遞給Activity的onTouchEvent處理.

@Override

public boolean onTouchEvent(MotionEvent event) {

 Toast.makeText(MainActivity.this, event.getAction() + "我是Activity", Toast.LENGTH_SHORT).show();

 return super.onTouchEvent(event);

}

當(dāng)你沒有給任何控件設(shè)置相應(yīng)事件的時(shí)候(也就是都會(huì)返回false),那么你就會(huì)看到Activity的onTouchEvent被調(diào)用了.
在開發(fā)藝術(shù)這本書中提到了幾個(gè)結(jié)論:
1.同一個(gè)事件序列是指手指接觸屏幕那一刻起,到手指離開屏幕那一刻結(jié)束,在這個(gè)過程中所有產(chǎn)生的事件都屬于這一個(gè)事件序列.包括一個(gè)ACTION_DOWN,一個(gè)ACTION_UP和n個(gè)ACTION_MOVE;

2.某一個(gè)View一旦決定攔截事件,那么這一事件序列都只能由它來處理.

這個(gè)結(jié)論認(rèn)真想了一下,似乎有點(diǎn)問題;假如這個(gè)View我設(shè)置了onTouchListener,但是我依然返回false,這個(gè)事件序列仍然會(huì)傳遞給父View,當(dāng)然了,這個(gè)View也只能接收到一個(gè)ACTION_DOWN事件,ACTION_UP和ACTION_MOVE不會(huì)接收到.假如這個(gè)View同時(shí)還設(shè)置了onClickListener,onTouchEvent返回false的時(shí)候事件會(huì)交給onTouchEvent處理這個(gè)事件,不會(huì)在交給父View處理了.這個(gè)問題還是需要結(jié)合源碼來看一下;
3.某個(gè)View一旦開始處理一個(gè)事件,如果它不消耗ACTION_DOWN事件,那么同一時(shí)間序列也不會(huì)交給他來處理了.

4.如果View不消耗ACTION_DWON以外的事件,那么這個(gè)點(diǎn)擊事件就會(huì)消失,不會(huì)在交還給父View處理.最后會(huì)交回activity處理.

5.ViewGroup默認(rèn)不攔截任何事件,他的onInterceptTouch方法默認(rèn)返回false;View沒有onInterceptTouch方法,收到事件他的onTouchEvent事件就會(huì)被調(diào)用.

6.View的onTouchEvent默認(rèn)都會(huì)消耗事件(返回true),除非他是不可點(diǎn)擊的(clickable和龍Clickable同時(shí)為false).View的longClickable默認(rèn)都是false,clickable分情況.

2.Activity對事件的分發(fā)

點(diǎn)擊事件用MotionEvent來表示,當(dāng)點(diǎn)擊事件發(fā)生的時(shí)候,事件最先傳遞給當(dāng)前Activity,由Activity的dispatchTouchEvent來進(jìn)行事件派發(fā),具體工作是由Activity內(nèi)部的Windows來處理的.Windows會(huì)將事件傳遞給decor view,即當(dāng)前View的root view.先看一下Activity的dispatchTouchEvent源碼:

public boolean diapatchTouchEvent(MotionEvent e) {

 if (e.getAction() == MotionEvent.ACTION_DOWN) {

 onUserInteraction();

 }

 if (getWindows.superDispatchTouchEvent()) {

 return true;

 }

 return onTouchEvent(e);

}

這里有個(gè)onUserInteraction
方法,點(diǎn)進(jìn)去發(fā)現(xiàn)這個(gè)方法是一個(gè)空方法,文檔是這樣寫的:

Called whenever a key, touch, or trackball event is dispatched to the activity. Implement this method if you wish to know that the user has interacted with the device in some way while your activity is running. This callback and onUserLeaveHint() are intended to help activities manage status bar notifications intelligently; specifically, for helping activities determine the proper time to cancel a notfication. All calls to your activity’s onUserLeaveHint() callback will be accompanied by calls to onUserInteraction(). This ensures that your activity will be told of relevant user activity such as pulling down the notification pane and touching an item there. Note that this callback will be invoked for the touch down action that begins a touch gesture, but may not be invoked for the touch-moved and touch-up actions that follow.

大體意思就是說onUserInteraction是幫助我們知道用戶開始和屏幕進(jìn)行交互的回調(diào)函數(shù).另外,還會(huì)和onUserLeaveHint
一起更加智能的管理狀態(tài)欄通知.

Called as part of the activity lifecycle when an activity is about to go into the background as the result of user choice. For example, when the user presses the Home key, onUserLeaveHint() will be called, but when an incoming phone call causes the in-call Activity to be automatically brought to the foreground, onUserLeaveHint() will not be called on the activity being interrupted. In cases when it is invoked, this method is called right before the activity’s onPause() callback. This callback and onUserInteraction() are intended to help activities manage status bar notifications intelligently; specifically, for helping activities determine the proper time to cancel a notfication.

這里這兩個(gè)方法對我們不是很重要了,根據(jù)分析可以知道,activity通過windows來分發(fā)事件,當(dāng)所有的view都沒有接收處理事件的時(shí)候,activity就會(huì)自己調(diào)用自己的onTouchEvent()來處理這個(gè)事件了.
繼續(xù)看getWindows.superDispatchTouchEvent()
,window
是個(gè)抽象類,

Abstract base class for a top-level window look and behavior policy. An instance of this class should be used as the top-level view added to the window manager. It provides standard UI policies such as a background, title area, default key processing, etc. The only existing implementation of this abstract class is android.view.PhoneWindow, which you should instantiate when needing a Window.

根據(jù)文檔的描述,我們可以知道window
的唯一實(shí)現(xiàn)類是android.view.PhoneWindow
,那么他的dispatchTouchEvent
是怎么實(shí)現(xiàn)的呢?

//PhoneWindow#superDispatchTouchEvent

public boolean superDispatchTouchEvent(MotionEvent e) {

 return mDecor.superDispatchTouchEvent(e);

}

這里的思路很清晰了,直接分發(fā)給mDecor處理,那么DecorView是什么東西呢?關(guān)于activity的層次點(diǎn)擊這里

//this is the top-level view of the window,containing the window decor(裝飾)

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker

private DecorView mDecor;

@Override

public final View getDecorView() {

 if (null == mDecor) {

 installDecor();

 }

 return mDecor;

}

到了這事件會(huì)繼續(xù)分發(fā),到我們通過setContentView設(shè)置的ViewGroup那里繼續(xù)處理.

3.ViewGroup對事件的分發(fā)

// Check for interception.

 final boolean intercepted;

 if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {

 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

 if (!disallowIntercept) {

 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;

 }

這段代碼是來判斷是否要來攔截當(dāng)前的點(diǎn)擊事件的.可以看出當(dāng)這個(gè)事件是一個(gè)事件序列的開端,也就是一個(gè)ACTION_DOWN,就用去調(diào)用onInterceptTouch方法去判斷是否要去攔截這個(gè)事件;或者當(dāng)mFirstTouchTarget不為空的時(shí)候,也會(huì)去判斷.相反,就不會(huì)攔截了.這也說明了一個(gè)事件如果View不去處理他的ACTION_DWON事件為什么就能在去處理其他的事件了.
當(dāng)ViewGroup不處理事件要繼續(xù)分發(fā)的時(shí)候代碼是這樣的:

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 = buildOrderedChildList();

final boolean customOrder = preorderedList == null

&& isChildrenDrawingOrderEnabled();

final View[] children = mChildren;

for (int i = childrenCount - 1; i >= 0; i--) {

 final int childIndex = customOrder

 ? getChildDrawingOrder(childrenCount, i) : i;

 final View child = (preorderedList == null)

 ? children[childIndex] : preorderedList.get(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;

 }

 if (!canViewReceivePointerEvents(child)

 || !isTransformedTouchPointInView(x, y, child, null)) {

 ev.setTargetAccessibilityFocus(false);

 continue;

}

newTouchTarget = getTouchTarget(child);

if (newTouchTarget != null) {

// Child is already receiving touch within its bounds.

// Give it the new pointer in addition to the ones it is handling.

 newTouchTarget.pointerIdBits |= idBitsToAssign;

 break;

}

resetCancelNextUpFlag(child);

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();

 newTouchTarget = addTouchTarget(child, idBitsToAssign);

 alreadyDispatchedToNewTouchTarget = true;

 break;

}

// The accessibility focus didn't handle the event, so clear

// the flag and do a normal dispatch to all children.

ev.setTargetAccessibilityFocus(false);

}

if (preorderedList != null) preorderedList.clear();

}

邏輯也比清晰,遍歷ViewGroup的所有子View,找出能接受事件的所有元素;要滿足兩個(gè)條件:1.坐標(biāo)是否落在子View中2.是否正在播放動(dòng)畫.滿足這兩個(gè)條件,就會(huì)分發(fā)給他來處理,要是返回了true就表示事件已經(jīng)被處理,mFirstTouchTarget就會(huì)被賦值并終止此次分發(fā),否則繼續(xù)分發(fā)過程.

4.View對點(diǎn)擊事件的處理過程

//View#dispatchTouchEvent

if (onFilterTouchEventForSecurity(event)) {

//noinspection SimplifiableIfStatement

 ListenerInfo li = mListenerInfo;

 if (li != null && li.mOnTouchListener != null

 && (mViewFlags & ENABLED_MASK) == ENABLED

 && li.mOnTouchListener.onTouch(this, event)) {

 result = true;

 }

 if (!result && onTouchEvent(event)) {

 result = true;

 }

}

這里相對比較簡單,首先判斷是否設(shè)置了onTouchListener,如果設(shè)置了就去調(diào)用onTouch方法,如果返回了false,則去調(diào)用onTouchEvent方法; 在view設(shè)置了onClickListener或者onLongClickListener后,會(huì)自動(dòng)將CLICKABLE或者LONG_CLICKABLE變成ture;

最后三張圖十分清晰

效果圖如下:
View不處理事件流程圖(View沒有消費(fèi)事件)


View處理事件
Touch interest
Touch interest

事件攔截
Touch Intercept
Touch Intercept

附錄

以后每個(gè)知識點(diǎn)的實(shí)踐學(xué)習(xí)代碼會(huì)上傳到我的GitHub,歡迎大家一起學(xué)習(xí)-.-~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,996評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內(nèi)容