Android事件分發(fā)機制?ViewGroup分析
前言:
事件分發(fā)從手指觸摸屏幕開始,即產(chǎn)生了觸摸信息,被底層系統(tǒng)捕獲后會傳遞給Android的輸入系統(tǒng)服務(wù)IMS
,通過Binder把消息發(fā)送到activity,activity會通過phoneWindow、DecorView最終發(fā)送給ViewGroup。這里就直接分析ViewGroup的事件分發(fā)
整體流程
配合圖在看一段偽代碼:
public boolean dispatchTouchEvent(MotionEvent ev) :Boolean{ val result = false //處理結(jié)果,默認(rèn)是沒消費過的 if (!onInterceptTouchEvent(ev)){ //是否攔截 result = child.dispatchTouchEvent(ev) // 分發(fā)給子view處理 } if (!result){ //事件沒有消費 if (onTouchListener != null) { //先詢問是否設(shè)置了onTouchListener result = onTouchListener.onTouch(ev) } if (!result) { //還是沒有消費就交給onTouchEvent處理 result = onTouchEvent(ev) } } return result }
這張圖和這段偽代碼實際上已經(jīng)概括了ViewGroup和View對事件處理的整個流程,注意只有ViewGroup有攔截機制即onInterceptTouchEvent
源碼分析
在分析源碼之前先了解個基本概念 同一事件序列
:同一個事件序列是指從手指接觸屏幕的那一刻起,到手指離開屏幕的那一刻結(jié)束,在這個過程中所產(chǎn)生的一系列事件,這個事件序列以down事件開始,中間含有數(shù)量不定的move事件,最終以up事件結(jié)束
public boolean dispatchTouchEvent(MotionEvent ev) { boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; /** * step1 * ACTION_DOWN是一個系列事件的起點,終點是ACTION_UP * 如果是ACTION_DOWN會重置一些flag并且會把mFirstTouchTarget置空 */ if (actionMasked == MotionEvent.ACTION_DOWN) { cancelAndClearTouchTargets(ev); resetTouchState(); } final boolean intercepted;//變量判斷消息是否被攔截 /** * step2 * 從以下代碼可以看出如果事件不是ACTION_DOWN并且mFirstTouchTarget為空的話那么ViewGroup是不能再攔截同一系列的事件了 * mFirstTouchTarget 代表的就是一個單鏈表,它會把處理當(dāng)前這一系列事件的view保存下來 * 假如當(dāng)前事件是ACTION_MOVE,并攔截了該事件那么會在step9中把mFirstTouchTarget置空 * * 結(jié)論1: * 如果View決定攔截一個事件那么該View的 onInterceptTouchEvent 方法不會再被調(diào)用了, * 同一序列事件后續(xù)的所有事件都只能由該View處理(當(dāng)然前提是事件能分發(fā)到該view,有可能在上層被攔截了) */ if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { /** * disallowIntercept表示是否禁用攔截功能,子view通過 requestDisallowInterceptTouchEvent 方法 * 可以要求父view不準(zhǔn)攔截事件,不過該方法在MotionEvent.ACTION_DOWN事件中不起作用,因為在step1中會把所有標(biāo)志位重置 * */ 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 { /** * 如果進(jìn)不到上面的if判斷則表示當(dāng)前系列事件viewGroup已經(jīng)攔截過某個事件了 * intercepted 直接置為true */ intercepted = true; } final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; final boolean isMouseEvent = ev.getSource() == InputDevice.SOURCE_MOUSE; final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0 && !isMouseEvent; TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; /** * step3 * 看這里如果ViewGroup攔截了該事件則不會進(jìn)入step3里面了,而是直接走到step9中 */ if (!canceled && !intercepted) { /** * step4 * 這里我們只考慮單指的點擊、移動和抬起 * ACTION_POINTER_DOWN和多點觸控有關(guān),ACTION_HOVER_MOVE和鼠標(biāo)有關(guān) * 所以如果當(dāng)前事件是MOVE也不會走step4也是直接走到step9中找到對應(yīng)的子view繼而分發(fā)事件 * 結(jié)論2:如果DOWN事件被某個view消耗那么后續(xù)的事件都會直接交給這個view(前提是父view沒有攔截) */ if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { final int actionIndex = ev.getActionIndex(); // always 0 for down final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) : TouchTarget.ALL_POINTER_IDS; final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { final float x = isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex); final float y = isMouseEvent ? ev.getYCursorPosition() : 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; /** * step5 * 遍歷所有的子view */ for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex); //省略部分代碼。。。 /** * step6 * 當(dāng)找到一個合適的子view時,在 dispatchTransformedTouchEvent 中會調(diào)用子view的dispatchTouchEvent * 如果該子view消耗了事件,會把子view保存到mFirstTouchTarget對應(yīng)的鏈表中,并結(jié)束for循環(huán) * * 結(jié)論3: * 如果一個view沒有消耗DOWN事件那么后續(xù)的事件都不會再分發(fā)給該view * 該結(jié)論和結(jié)論2呼應(yīng)上了,因為在這個for循環(huán)中只有子view的 dispatchTransformedTouchEvent返回true才會被加入到鏈表中 * 下一次的事件并不會再到step4中來了 */ 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(); /** * step7 * 把子view保存到鏈表中,mFirstTouchTarget指向表頭 * alreadyDispatchedToNewTouchTarget置為true * 結(jié)束for循環(huán) */ 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); } } } } /** * step8 * 如果攔截了事件會把 mFirstTouchTarget 置空這個時候就直接調(diào)用viewGroup的super.dispatchTouchEvent * 即view中的dispatchTouchEvent */ if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { // Dispatch to touch targets, excluding the new touch target if we already // dispatched to it. Cancel touch targets if necessary. TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; /** * step9 * 如果攔截了就把mFirstTouchTarget置空,沒有攔截就找到對應(yīng)的childView把事件分發(fā)下去 */ while (target != null) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; //注意這里cancelChild如果為true,并且target.child不為空的話,dispatchTransformedTouchEvent會把事件轉(zhuǎn)成CANCEL分發(fā)給target.child 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; } } } return handled; }
看下cancel事件的由來,這里需要結(jié)合上文代碼step9看
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; final int oldAction = event.getAction(); if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { /** * 把事件轉(zhuǎn)換成ACTION_CANCEL */ event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) { handled = super.dispatchTouchEvent(event); } else { /** * 如果child不為空就分發(fā)給它 */ handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; } return handled; }
到此這篇關(guān)于Android事件分發(fā)機制 ViewGroup分析的文章就介紹到這了,更多相關(guān)Android ViewGroup內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android中的Retrofit+OkHttp+RxJava緩存架構(gòu)使用
Retrofit和OkHttp API以及JVM擴展RxJava都是開源項目,大家可以輕松在GitHub上找到,下載和基本配置部分這里我們不作重點,主要還是來看一下Android中的Retrofit+OkHttp+RxJava緩存架構(gòu)使用:2016-06-06Android應(yīng)用開發(fā)中使用Fragment的入門學(xué)習(xí)教程
這篇文章主要介紹了Android應(yīng)用開發(fā)中Fragment的入門學(xué)習(xí)教程,可以把Fragment看作為Activity基礎(chǔ)之上的模塊,需要的朋友可以參考下2016-02-02Android launcher中模擬按home鍵的實現(xiàn)
這篇文章主要介紹了Android launcher中模擬按home鍵的實現(xiàn)的相關(guān)資料,需要的朋友可以參考下2017-05-05限時搶購秒殺系統(tǒng)架構(gòu)分析與實戰(zhàn)
這篇文章主要介紹了限時搶購秒殺系統(tǒng)架構(gòu)分析與實戰(zhàn) 的相關(guān)資料,需要的朋友可以參考下2016-01-01Android入門之使用SQLite內(nèi)嵌式數(shù)據(jù)庫詳解
Android內(nèi)帶SQLite內(nèi)嵌式數(shù)據(jù)庫了。這對于我們存儲一些更復(fù)雜的結(jié)構(gòu)化數(shù)據(jù)帶來了極大的便利。本文就來和大家聊聊具體的使用方法,希望對大家有所幫助2022-12-12Android時間日期拾取器學(xué)習(xí)使用(DatePicker、TimePicker)
這篇文章主要為大家詳細(xì)介紹了Android提供的DatePicker日期拾取器和TimePicker時間拾取器的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-02-02Android ListView實現(xiàn)簡單列表功能
這篇文章主要為大家詳細(xì)介紹了Android ListView實現(xiàn)簡單列表功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-08-08