Android事件分發(fā)機(jī)制?ViewGroup分析
前言:
事件分發(fā)從手指觸摸屏幕開始,即產(chǎn)生了觸摸信息,被底層系統(tǒng)捕獲后會(huì)傳遞給Android的輸入系統(tǒng)服務(wù)IMS
,通過Binder把消息發(fā)送到activity,activity會(huì)通過phoneWindow、DecorView最終發(fā)送給ViewGroup。這里就直接分析ViewGroup的事件分發(fā)
整體流程
配合圖在看一段偽代碼:
public boolean dispatchTouchEvent(MotionEvent ev) :Boolean{ val result = false //處理結(jié)果,默認(rèn)是沒消費(fèi)過的 if (!onInterceptTouchEvent(ev)){ //是否攔截 result = child.dispatchTouchEvent(ev) // 分發(fā)給子view處理 } if (!result){ //事件沒有消費(fèi) if (onTouchListener != null) { //先詢問是否設(shè)置了onTouchListener result = onTouchListener.onTouch(ev) } if (!result) { //還是沒有消費(fèi)就交給onTouchEvent處理 result = onTouchEvent(ev) } } return result }
這張圖和這段偽代碼實(shí)際上已經(jīng)概括了ViewGroup和View對(duì)事件處理的整個(gè)流程,注意只有ViewGroup有攔截機(jī)制即onInterceptTouchEvent
源碼分析
在分析源碼之前先了解個(gè)基本概念 同一事件序列
:同一個(gè)事件序列是指從手指接觸屏幕的那一刻起,到手指離開屏幕的那一刻結(jié)束,在這個(gè)過程中所產(chǎn)生的一系列事件,這個(gè)事件序列以down事件開始,中間含有數(shù)量不定的move事件,最終以u(píng)p事件結(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是一個(gè)系列事件的起點(diǎn),終點(diǎn)是ACTION_UP * 如果是ACTION_DOWN會(huì)重置一些flag并且會(huì)把mFirstTouchTarget置空 */ if (actionMasked == MotionEvent.ACTION_DOWN) { cancelAndClearTouchTargets(ev); resetTouchState(); } final boolean intercepted;//變量判斷消息是否被攔截 /** * step2 * 從以下代碼可以看出如果事件不是ACTION_DOWN并且mFirstTouchTarget為空的話那么ViewGroup是不能再攔截同一系列的事件了 * mFirstTouchTarget 代表的就是一個(gè)單鏈表,它會(huì)把處理當(dāng)前這一系列事件的view保存下來 * 假如當(dāng)前事件是ACTION_MOVE,并攔截了該事件那么會(huì)在step9中把mFirstTouchTarget置空 * * 結(jié)論1: * 如果View決定攔截一個(gè)事件那么該View的 onInterceptTouchEvent 方法不會(huì)再被調(diào)用了, * 同一序列事件后續(xù)的所有事件都只能由該View處理(當(dāng)然前提是事件能分發(fā)到該view,有可能在上層被攔截了) */ if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { /** * disallowIntercept表示是否禁用攔截功能,子view通過 requestDisallowInterceptTouchEvent 方法 * 可以要求父view不準(zhǔn)攔截事件,不過該方法在MotionEvent.ACTION_DOWN事件中不起作用,因?yàn)樵趕tep1中會(huì)把所有標(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)攔截過某個(gè)事件了 * 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攔截了該事件則不會(huì)進(jìn)入step3里面了,而是直接走到step9中 */ if (!canceled && !intercepted) { /** * step4 * 這里我們只考慮單指的點(diǎn)擊、移動(dòng)和抬起 * ACTION_POINTER_DOWN和多點(diǎn)觸控有關(guān),ACTION_HOVER_MOVE和鼠標(biāo)有關(guān) * 所以如果當(dāng)前事件是MOVE也不會(huì)走step4也是直接走到step9中找到對(duì)應(yīng)的子view繼而分發(fā)事件 * 結(jié)論2:如果DOWN事件被某個(gè)view消耗那么后續(xù)的事件都會(huì)直接交給這個(gè)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)找到一個(gè)合適的子view時(shí),在 dispatchTransformedTouchEvent 中會(huì)調(diào)用子view的dispatchTouchEvent * 如果該子view消耗了事件,會(huì)把子view保存到mFirstTouchTarget對(duì)應(yīng)的鏈表中,并結(jié)束for循環(huán) * * 結(jié)論3: * 如果一個(gè)view沒有消耗DOWN事件那么后續(xù)的事件都不會(huì)再分發(fā)給該view * 該結(jié)論和結(jié)論2呼應(yīng)上了,因?yàn)樵谶@個(gè)for循環(huán)中只有子view的 dispatchTransformedTouchEvent返回true才會(huì)被加入到鏈表中 * 下一次的事件并不會(huì)再到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 * 如果攔截了事件會(huì)把 mFirstTouchTarget 置空這個(gè)時(shí)候就直接調(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置空,沒有攔截就找到對(duì)應(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會(huì)把事件轉(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ā)機(jī)制 ViewGroup分析的文章就介紹到這了,更多相關(guān)Android ViewGroup內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android中的Retrofit+OkHttp+RxJava緩存架構(gòu)使用
Retrofit和OkHttp API以及JVM擴(kuò)展RxJava都是開源項(xiàng)目,大家可以輕松在GitHub上找到,下載和基本配置部分這里我們不作重點(diǎn),主要還是來看一下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鍵的實(shí)現(xiàn)
這篇文章主要介紹了Android launcher中模擬按home鍵的實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下2017-05-05限時(shí)搶購秒殺系統(tǒng)架構(gòu)分析與實(shí)戰(zhàn)
這篇文章主要介紹了限時(shí)搶購秒殺系統(tǒng)架構(gòu)分析與實(shí)戰(zhàn) 的相關(guān)資料,需要的朋友可以參考下2016-01-01Android酷炫動(dòng)畫效果之3D星體旋轉(zhuǎn)效果
本文要實(shí)現(xiàn)的3D星體旋轉(zhuǎn)效果是從CoverFlow演繹而來,不過CoverFlow只是對(duì)圖像進(jìn)行轉(zhuǎn)動(dòng),我這里要實(shí)現(xiàn)的效果是要對(duì)所有的View進(jìn)行類似旋轉(zhuǎn)木馬的轉(zhuǎn)動(dòng)2018-05-05Android入門之使用SQLite內(nèi)嵌式數(shù)據(jù)庫詳解
Android內(nèi)帶SQLite內(nèi)嵌式數(shù)據(jù)庫了。這對(duì)于我們存儲(chǔ)一些更復(fù)雜的結(jié)構(gòu)化數(shù)據(jù)帶來了極大的便利。本文就來和大家聊聊具體的使用方法,希望對(duì)大家有所幫助2022-12-12Android時(shí)間日期拾取器學(xué)習(xí)使用(DatePicker、TimePicker)
這篇文章主要為大家詳細(xì)介紹了Android提供的DatePicker日期拾取器和TimePicker時(shí)間拾取器的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-02-02Android ListView實(shí)現(xiàn)簡單列表功能
這篇文章主要為大家詳細(xì)介紹了Android ListView實(shí)現(xiàn)簡單列表功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08