Android事件分發(fā)機(jī)制示例分析
- Android事件類型
public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_MOVE: break; case MotionEvent.ACTION_UP: break; case MotionEvent.ACTION_CANCEL: break; } return true; }
如上圖所示android主要有四種事件類型,Down、Move、Up、Cancel。
在一次事件流中:
Down觸發(fā)一次,手指在屏幕按下時(shí)。
Move事件會(huì)觸發(fā)0次或多次,手指在屏幕上滑動(dòng)時(shí)。
Up事件會(huì)觸發(fā)0次或一次,手指在屏幕抬起時(shí)。
Cancel事件會(huì)觸發(fā)0次或一次,滑動(dòng)超出空間邊界時(shí)。
- android App層事件分發(fā)機(jī)制調(diào)用鏈?zhǔn)窃趺礃拥模?/li>
App層事件分發(fā)的入口在Activity中,dispatchTouchEvent是在Framework層的WindowManagerService掉用的,WMS中的事件是來源于Native層,Linux層,這塊的事件傳遞有機(jī)會(huì)在寫文章進(jìn)行分析,這篇文章主要分析Android事件分發(fā)機(jī)制在App成是如何展現(xiàn)的。
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }
getWindow()返回的是PhoneWindow對(duì)象。
@Override public boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event); }
public boolean superDispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); }
mDecor是DecorView,是PhoneWindow的一個(gè)內(nèi)部類,繼承FrameLayout。
FrameLayout繼承自ViewGroup,接下來看事件傳遞在ViewGroup中是如何處理的。事件傳遞的主要邏輯就在ViewGroup中。
接下來看ViewGroup,dispatchToucheEvent(MotionEvent ev)是如何對(duì)事件進(jìn)行分發(fā)的。
public class ViewGroup { @Override public boolean dispatchTouchEvent(MotionEvent ev) { boolean handled = false; final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; // Check for interception. //按下后,第一次調(diào)用時(shí)actionMasked=down,mFirstTouchTarget=null //一直按著 actionMasked=move, 如果mFirstTouchTarget!=null說明有子view處理了touch事件 final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { //調(diào)用子類復(fù)寫的方法 如果為true表示攔截,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. //如果沒有子view處理touch事件,并且不是第一次按下的down事件,group會(huì)對(duì)touch事件進(jìn)行攔截 intercepted = true; } TouchTarget newTouchTarget = null; //第一次按下時(shí),判斷view是否處理事件的標(biāo)志 boolean alreadyDispatchedToNewTouchTarget = false; //如果不攔截,則進(jìn)入事件分發(fā) if (!canceled && !intercepted) { //注意,只有ACTION_DOWN時(shí)才會(huì)遍歷子View if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { final ArrayList<View> preorderedList = buildTouchDispatchChildList(); final View[] children = mChildren; //開始遍歷子view for (int i = childrenCount - 1; i >= 0; i--) { final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex); //isTransformedTouchPointInView 通過比對(duì)坐標(biāo),來判斷事件是否落在了child上, // true是,false不是 if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); // 如果事件沒有落在child上,則跳過此次循環(huán),執(zhí)行下一次循環(huán) continue; } //通過遍歷TouchTarget鏈表查找TouchTarget,如果找到了,則break跳出循環(huán) 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; } //第一次進(jìn)來newTouchTarget==null,進(jìn)入下面的邏輯 //如果dispatchTransformedTouchEvent 返回true,表示子view對(duì)事件進(jìn)行了處理,則跳出此循環(huán), // 不會(huì)再向子view進(jìn)行分發(fā) if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // Child wants to receive touch within its bounds. //將child緩存到TouchTarget的鏈表中 newTouchTarget = addTouchTarget(child, idBitsToAssign); //注意這個(gè)標(biāo)志位,true,下面的邏輯會(huì)用到 alreadyDispatchedToNewTouchTarget = true; break; } } if (preorderedList != null) preorderedList.clear(); } } } // Dispatch to touch targets. //ACTION_DOWN和ACTION_MOVE 的時(shí)都會(huì)執(zhí)行下面邏輯 //mFirstTouchTarget==null 說明沒有子View處理touch事件,dispatchTransformedTouchEvent的第三個(gè)參數(shù)為null。 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. //對(duì) TouchTarget 鏈表進(jìn)行遍歷,TouchTarget里緩存了處理事件的view, //一次完整事件,move會(huì)調(diào)用多次,所以在ACTION_DOWN時(shí)找到的對(duì)應(yīng)的view,緩存起來, // 而不是每次都遍歷ViewGroup的子View,這樣太損壞性能 TouchTarget target = mFirstTouchTarget; //開始對(duì)鏈表進(jìn)行遍歷,為啥是一個(gè)鏈表呢,這是為了進(jìn)行多點(diǎn)觸摸,會(huì)有多個(gè)view響應(yīng)觸摸事件。 while (target != null) { final TouchTarget next = target.next; //alreadyDispatchedToNewTouchTarget ==true ,并且緩存的target和newTouchTarget是同一個(gè) //直接將handled置為true,防止ACTION_DOWN時(shí)dispatchTransformedTouchEvent方法重復(fù)調(diào)用 if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } } target = next; } } //將handled 返回dispatchTouchEvent的調(diào)用者。 return handled; } /** * Gets the touch target for specified child view. * Returns null if not found. * 通過遍歷TouchTarget 鏈表,找到touch target */ private TouchTarget getTouchTarget(@NonNull View child) { for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) { if (target.child == child) { return target; } } return null; } /** * Adds a touch target for specified child to the beginning of the list. * Assumes the target child is not already present. * 將TouchTarget和view進(jìn)行關(guān)聯(lián),并且把這個(gè)TouchTarget放在鏈表的頭部 */ private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) { final TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; mFirstTouchTarget = target; return target; } private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; // 只看最關(guān)鍵的兩行代碼 if (child == null) { //如果child為null,沒有子view處理touch事件,則調(diào)用父類View的dispatchTouchEvent。 handled = super.dispatchTouchEvent(transformedEvent); } else { //如果child不為空,則調(diào)用child view 的dispatchTouchEvent //這行代碼很關(guān)鍵,如果child是ViewGroup則繼續(xù)在ViewGoup的進(jìn)行事件分發(fā),這會(huì)是一個(gè)遞歸調(diào)用 //如果child 是一個(gè)View,則調(diào)用View的dispatchTouchEvent,進(jìn)行事件的分發(fā)。 handled = child.dispatchTouchEvent(transformedEvent); } //以上兩行代碼最終會(huì)調(diào)用到View的dispatchTouchEvent方法。 return handled; } }
接下來看View dispatchTouchEvent如何處理的
public class View { public boolean dispatchTouchEvent(MotionEvent ev) { boolean result = false; ListenerInfo li = mListenerInfo; //如果view設(shè)置了mOnTouchListener,先調(diào)用mOnTouchListener.onTouch, //如果返回true,則表示消費(fèi)了此事件,不在向下傳遞,為啥這樣說?那是因?yàn)?,通過他的返回值,進(jìn)行了邏輯判斷 if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } //如果result為false,則調(diào)用View.onTouchEvent方法 if (!result && onTouchEvent(event)) { result = true; } return result; } public boolean onTouchEvent(MotionEvent event) { switch (action) { case MotionEvent.ACTION_UP: if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { //調(diào)用performClick mOnClickListener.onClick()方法 performClickInternal(); } break; case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_CANCEL: break; case MotionEvent.ACTION_MOVE: break; } return false; } public boolean performClick() { final boolean result; final ListenerInfo li = mListenerInfo; if (li != null && li.mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this); result = true; } else { result = false; } return result; } }
如果仔細(xì)看了ViewGroup和View事件分發(fā)邏輯代碼。我們可以總結(jié)為以下幾點(diǎn)。
1.ViewGroup接收到DispatchTouchEvent調(diào)用后,先調(diào)用了onInterceptTouchEvent,通過返回值進(jìn)行判斷是否對(duì)事件進(jìn)行攔截。我們可以在代碼中復(fù)寫這個(gè)方法來進(jìn)行事件的攔截。
2.down事件后,ViewGoup會(huì)遍歷子View,找到處理Touch事件的View,并且緩存到了TouchTarget鏈表中,一次事件流中,只有down的時(shí)候才會(huì)進(jìn)行子View的遍歷。
只有做的好處提升了性能,緩存的鏈表中也是為了多點(diǎn)觸摸,會(huì)有多個(gè)view響應(yīng)觸摸事件。
move事件時(shí),則會(huì)從這個(gè)鏈表中進(jìn)行遍歷,通過調(diào)用dispatchTransformedTouchEvent方法進(jìn)行事件的傳遞。
- 在dispatchTransformedTouchEvent方法中,根據(jù)child的值是否為null進(jìn)入不同的分發(fā)流程。
1)如果為null說明沒有子view沒有響應(yīng)事件,則調(diào)用父類View的dispatchTouchEvent--->onTouchEvent方法。
2)如果child值不為null。則調(diào)用child的dispatchTouchEvent方法,
此時(shí)如果child是一個(gè)ViewGoup則還是執(zhí)行ViewGoup中的分發(fā)邏輯,進(jìn)行遞歸調(diào)用。
如果child是一個(gè)View,則調(diào)用View的dispatchTouchEvent--->onTouchEvent方法。
- 在View的dispatchTouchEvent方法中:
1)listener的onTouch優(yōu)先級(jí)要高于View的onTouchEvent
2)會(huì)先調(diào)用mOnTouchListener.onTouch方法,并根據(jù)返回值進(jìn)行判斷是否消費(fèi)事件。
3)如果上面的返回值為false,則會(huì)調(diào)用View的onTouchEvent方法。
5.在View的onTouchEvent方法中UP事件時(shí),才會(huì)調(diào)用View設(shè)置的onClickListener的監(jiān)聽事件。
這就是為什么了,同時(shí)給一個(gè)View設(shè)置了onTouch監(jiān)聽和lonClickListener監(jiān)聽時(shí),當(dāng)onTouch返回true無法調(diào)用onClick事件的原因。
到此這篇關(guān)于Android事件分發(fā)機(jī)制示例分析的文章就介紹到這了,更多相關(guān)Android事件分發(fā)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android手機(jī)App安全漏洞整理(小結(jié))
這篇文章主要介紹了Android手機(jī)App安全漏洞整理(小結(jié)),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-09-09Android Room數(shù)據(jù)庫(kù)容易遇到的問題以及解決方法
這篇文章給大家介紹了我們?cè)贏ndroid Room數(shù)據(jù)庫(kù)容易遇到的坑以及解決方法,文中有詳細(xì)的代碼示例供我們參考,具有一定的參考價(jià)值,需要的朋友可以參考下2023-09-09EditText實(shí)現(xiàn)輸入限制和校驗(yàn)功能實(shí)例代碼
本文通過實(shí)例代碼給大家介紹EditText實(shí)現(xiàn)輸入限制和校驗(yàn)功能,感興趣的朋友參考下吧2017-08-08Android中查看USB連接的外接設(shè)備信息的代碼實(shí)例
這篇文章主要介紹了Android中查看USB連接的外接設(shè)備信息的代碼實(shí)例,需要的朋友可以參考下2014-04-04Android中利用SurfaceView制作抽獎(jiǎng)轉(zhuǎn)盤的全流程攻略
這篇文章主要介紹了Android中利用SurfaceView制作抽獎(jiǎng)轉(zhuǎn)盤的全流程,從圖案的繪制到轉(zhuǎn)盤的控制再到布局,真的非常全面,需要的朋友可以參考下2016-04-04Android使用Gridview單行橫向滾動(dòng)顯示
這篇文章主要為大家詳細(xì)介紹了Android使用Gridview單行橫向滾動(dòng)顯示,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07Android屏幕適配工具類 Android自動(dòng)生成不同分辨率的值
這篇文章主要為大家詳細(xì)介紹了Android屏幕適配工具類,Android自動(dòng)生成不同分辨率的值,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-03-03