Android View的事件分發(fā)機(jī)制深入分析講解
1.分發(fā)對(duì)象-MotionEvent
事件類型有:
1.ACTION_DOWN-----手指剛接觸屏幕
2.ACTION_MOVE------手指在屏幕上移動(dòng)
3.ACTION_UP------手指從屏幕上松開的一瞬間
4.ACTION_CANCEL-----事件被上層攔截時(shí)觸發(fā)
MotionEvent主要的方法:
getX() | 得到事件發(fā)生的x軸坐標(biāo)(相對(duì)于當(dāng)前視圖) |
getY() | 得到事件發(fā)生的y軸坐標(biāo)(相對(duì)于當(dāng)前視圖) |
getRawX() | 得到事件發(fā)生的x軸坐標(biāo)(相對(duì)于屏幕左頂點(diǎn)) |
getRawY() | 得到事件發(fā)生的y軸坐標(biāo)(相對(duì)于屏幕左頂點(diǎn)) |
2.如何傳遞事件
1.傳遞流程
底層IMS->ViewRootImpl->activity->viewgroup->view
2.事件分發(fā)的源碼解析
1.Activity對(duì)點(diǎn)擊事件的分發(fā)過(guò)程
Activity#dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } //事件交給Activity所附屬的Window進(jìn)行分發(fā),如果返回true,循環(huán)結(jié)束,返回false,沒(méi)人處理 if (getWindow().superDispatchTouchEvent(ev)) { return true; } //所有View的onTouchEvent都返回false,那么Activity的onTouchEvent就會(huì)被調(diào)用 return onTouchEvent(ev); }
Window#superDispatchTouchEvent
public abstract boolean superDispatchTouchEvent(MotionEvent event);
PhoneWindow#superDispatchTouchEvent
public boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event); }
DecorView#superDispatchTouchEvent()
public boolean superDispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); }
ViewGroup#dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->
2.頂級(jí)View對(duì)點(diǎn)擊事件的分發(fā)過(guò)程
把ViewGroup的dispatchTouchEvent()方法中的代碼進(jìn)行分段說(shuō)明
第一段:
描述的是View是否攔截點(diǎn)擊事件這個(gè)邏輯
// Check for interception. final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {//事件類型為down或者mFirstTouchTarget有值 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { intercepted = onInterceptTouchEvent(ev);//詢問(wèn)是否攔截,方法返回true就攔截 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)事件類型為down或者mFirstTouchTarget有值時(shí),就不攔截當(dāng)前事件,否則直接攔截了這個(gè)事件。那么mFirstTouchTarget什么時(shí)候有值?當(dāng)ViewGroup不攔截事件并且把事件交給子元素處理時(shí),mFirstTouchTarget就有值并且指向子元素。所以當(dāng)事件類型為down并且攔截事件,那么mFirstTouchTarget為空,這會(huì)讓后面的事件move和up無(wú)法滿足mFirstTouchTarget有值的條件,直接無(wú)法調(diào)用onInterceptTouchEvent方法。
特殊情況:通過(guò)requestDisallowInterceptTouchEvent方法來(lái)設(shè)置標(biāo)記位FLAG_DISALLOW_INTERCEPT,ViewGroup就無(wú)法攔截除了ACTION_DOWN以外的點(diǎn)擊事件,這個(gè)標(biāo)記位無(wú)法影響ACTION_DOWN事件,因?yàn)楫?dāng)事件為ACTION_DOWN時(shí),就會(huì)重置這個(gè)標(biāo)記位,將導(dǎo)致子View設(shè)置的這個(gè)標(biāo)記位無(wú)效。
總結(jié):
1.當(dāng)ViewGroup決定攔截事件后,那么后續(xù)的點(diǎn)擊事件將會(huì)默認(rèn)交給它處理并且不再調(diào)用它的onInterceptTouchEvent方法。
2.當(dāng)ViewGroup不攔截ACTION_DOWN事件,那么標(biāo)記位FLAG_DISALLOW_INTERCEPT讓ViewGroup不再攔截事件。
第二段:
當(dāng)ViewGroup不攔截事件時(shí),分發(fā)事件給子View,看哪個(gè)子View處理事件
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. //對(duì)子元素進(jìn)行排序 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; } if (!child.canReceivePointerEvents() || !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; }
遍歷ViewGroup的所有的子元素,判斷子元素是否在播動(dòng)畫和點(diǎn)擊事件的坐標(biāo)是否落在子元素的區(qū)域內(nèi),如果是,就能接收到點(diǎn)擊事件,并且事件會(huì)傳遞給它來(lái)處理。
我們來(lái)看一下dispatchTransformedTouchEvent方法
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { ... if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } ... }
dispatchTransformedTouchEvent實(shí)際上調(diào)用的是子元素的dispatchTouchEvent方法。
如果子元素的dispatchTouchEvent方法返回true,那么mFirstTouchTarget就會(huì)被賦值同時(shí)跳出for循環(huán)
newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break;
這幾行代碼完成了mFirstTouchTarget的賦值并終止對(duì)子元素的遍歷,如果子元素的dispatchTouchEvent方法返回false,那么ViewGroup就會(huì)把事件分發(fā)給下一個(gè)子元素。
其實(shí)mFirstTouchTarget真正的賦值是在addTouchTarget方法里面,mFirstTouchTarget是一種單鏈表結(jié)構(gòu)。
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) { final TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; mFirstTouchTarget = target; return target; }
第三段:
執(zhí)行事件
//當(dāng)前View的事件處理代碼 if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { //子View的事件處理代碼 ...
dispatchTransformedTouchEvent方法的第三個(gè)參數(shù)為null,則會(huì)調(diào)用super.dispatchTouchEvent方法,也就是View的dispatchTouchEvent方法,所以點(diǎn)擊事件給View處理。
View對(duì)點(diǎn)擊事件的處理過(guò)程
View(不包含ViewGroup)是一個(gè)單獨(dú)的元素,沒(méi)有子元素,只能自己處理事件。
public boolean dispatchTouchEvent(MotionEvent event) { ... boolean result = false; ... if (onFilterTouchEventForSecurity(event)) { if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) { result = true; } //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; } } ... return result; }
首先判斷是否設(shè)置了OnTouchListener,如果OnTouchListener的onTouch方法返回true,就不會(huì)調(diào)用onTouchEvent方法,否則就會(huì)調(diào)用onTouchEvent方法。
public boolean onTouchEvent(MotionEvent event) { ... if ((viewFlags & ENABLED_MASK) == DISABLED && (mPrivateFlags4 & PFLAG4_ALLOW_CLICK_WHEN_DISABLED) == 0) { if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. return clickable; } ... }
不可用狀態(tài)下的View照樣會(huì)消耗點(diǎn)擊事件
switch (action) { case MotionEvent.ACTION_UP: mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; if ((viewFlags & TOOLTIP) == TOOLTIP) { handleTooltipUp(); } ... if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { ... if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) { removeLongPressCallback(); // Only perform take click actions if we were in the pressed state if (!focusTaken) { // Use a Runnable and post this rather than calling // performClick directly. This lets other visual state // of the view update before click actions start. if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { performClickInternal(); } } } } ... mIgnoreNextUpEvent = false; break;
當(dāng)ACTION_UP事件發(fā)生時(shí),會(huì)觸發(fā)performClick方法,如果View設(shè)置了OnClickListener,那么performClick方法內(nèi)部會(huì)調(diào)用它的onClick方法。
private boolean performClickInternal() { // Must notify autofill manager before performing the click actions to avoid scenarios where // the app has a click listener that changes the state of views the autofill service might // be interested on. notifyAutofillManagerOnClick(); return performClick(); }
public boolean performClick() { final boolean result; final ListenerInfo li = mListenerInfo; //關(guān)鍵代碼,判斷是否設(shè)置了onClickListener if (li != null && li.mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this); result = true; } else { result = false; } ... return result;//最終返回執(zhí)行結(jié)果 }
點(diǎn)擊事件的分發(fā)機(jī)制的源碼實(shí)現(xiàn)已經(jīng)分析完了。
3.主要方法
1.dispatchTouchEvent:用來(lái)進(jìn)行事件的分發(fā),如果事件可以傳遞給當(dāng)前View,那么此方法一定會(huì)被調(diào)用,返回結(jié)果受當(dāng)前View的onTouchEvent和下級(jí)View的dispatchTouchEvent方法的影響,表示是否消耗當(dāng)前事件。
2.onInterceptTouchEvent:用來(lái)判斷是否攔截某個(gè)事件,如果當(dāng)前View攔截了某個(gè)事件,那么在同一事件序列當(dāng)中,此方法不會(huì)被再次調(diào)用,返回結(jié)果表示是否攔截當(dāng)前事件。
3.onTouchEvent:用來(lái)處理點(diǎn)擊事件,返回結(jié)果表示是否消耗當(dāng)前事件,如果不消耗,則在同一個(gè)事件序列中,當(dāng)前View無(wú)法再次接收到事件。
4.requestDisallowInterceptTouchEvent:一般用于子View中,要求父View不攔截事件。
5.dispatchTransformedTouchEvent:如果child不為空,就發(fā)到child的dispatchTouchEvent中,否則發(fā)給自己。
4.事件傳遞中l(wèi)istener
onTouch,performClick和onClick調(diào)用的順序以及onTouch返回值的影響?
當(dāng)一個(gè)View需要處理事件時(shí),View的dispatchTouchEvent方法中,如果設(shè)置了OnTouchListener,那么OnTouchListener的onTouch方法會(huì)被調(diào)用,當(dāng)onTouch方法返回true時(shí),onTouchEvent就不會(huì)被調(diào)用,當(dāng)onTouch方法返回false時(shí),onTouchEvent方法就被調(diào)用,在onTouchEvent方法里面進(jìn)入performClick方法,在performClick方法里面判斷是否設(shè)置onClickListener,并且如果設(shè)置了onClickListener,那么onClick方法就被調(diào)用,performClick方法就返回true,如果沒(méi)有設(shè)置了onClickListener,performClick方法就返回false。
總的來(lái)說(shuō)方法調(diào)用的順序?yàn)?/p>
5.滑動(dòng)沖突如何用事件分發(fā)處理
滑動(dòng)沖突定義:當(dāng)有內(nèi)外兩層View都可以響應(yīng)事件時(shí),事件由誰(shuí)來(lái)決定。
滑動(dòng)沖突類型:1.當(dāng)內(nèi)外兩層View滑動(dòng)方向不一致
2.當(dāng)內(nèi)外兩層滑動(dòng)方向一致的時(shí)候
3.兩種情況疊加
解決思路:
內(nèi)部攔截:dispatchTouchEvent+dispatchTransformedTouchEvent
重寫子元素的dispatchTouchEvent方法
down事件分發(fā)給子元素,move事件是看條件的,如果不滿足條件,就把事件交給子元素處理,如果滿足條件,就會(huì)取消子元素的處理事件,然后把事件交給父元
public boolean dispatchTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { //down事件,父容器不要攔截我 parent.requestDisallowInterceptTouchEvent(true); break; } case MotionEvent.ACTION_MOVE: { int deltaX = x - mLastX; int deltaY = y - mLastY; if (父容器需要此類點(diǎn)擊事件) { //父容器攔截我 parent.requestDisallowInterceptTouchEvent(false); } break; } case MotionEvent.ACTION_UP: { break; } default: break; } mLastX = x; mLastY = y; return super.dispatchTouchEvent(event); }
(當(dāng)move事件時(shí),進(jìn)入第一塊代碼,調(diào)用intercepted = onInterceptTouchEvent(ev),我們?cè)趏nInterceptTouchEvent方法中設(shè)置不是down事件就返回true,所以intercepted為true,然后第二塊代碼不會(huì)執(zhí)行,進(jìn)入第三塊代碼,因?yàn)閕ntercepted為true,所以cancelChild就為true,取消子元素事件執(zhí)行,調(diào)用dispatchTransformedTouchEvent方法,cancel為true->
event.setAction(MotionEvent.ACTION_CANCEL)->handled = child.dispatchTouchEvent(event)
把mFirstTouchTarget設(shè)置為空,所以到下一個(gè)move事件來(lái)的時(shí)候,mFirstTouchTarget是為空的,在第一段代碼中intercepted為true,第二段代碼不執(zhí)行,第三塊代碼走dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS),即由當(dāng)前View的事件處理代碼(父元素))
重寫父元素的onInterceptTouchEvent方法
當(dāng)為down事件時(shí),要return false,因?yàn)樵赩iewGroup的dispatchTouchEvent方法中,當(dāng)為down事件時(shí),會(huì)調(diào)用resetTouchState()方法,在resetTouchState()方法里面會(huì)重置狀態(tài),把mGroupFlags也重置,這樣會(huì)導(dǎo)致在前面的parent.requestDisallowInterceptTouchEvent(true)沒(méi)有用,所以我們?cè)趏nInterceptTouchEvent方法里面要設(shè)置為down事件時(shí)返回false,因?yàn)樵赿own事件時(shí)onInterceptTouchEvent一定會(huì)執(zhí)行。
public boolean onInterceptTouchEvent(MotionEvent event) { int action = event.getAction(); if (action == MotionEvent.ACTION_DOWN) { super.onInterceptTouchEvent(event); return false; } else { return true; } }
外部攔截:onInterceptTouchEvent
點(diǎn)擊事件先經(jīng)過(guò)父容器的攔截處理,如果父容器需要這件事就攔截,不需要就不攔截。
public boolean onInterceptTouchEvent(MotionEvent event) { boolean intercepted = false; int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { intercepted = false; break; } case MotionEvent.ACTION_MOVE: { if (滿足父容器的攔截要求) { intercepted = true; } else { intercepted = false; } break; } case MotionEvent.ACTION_UP: { intercepted = false; break; } default: break; } mLastXIntercept = x; mLastYIntercept = y; return intercepted; }
到此這篇關(guān)于Android View的事件分發(fā)機(jī)制深入分析講解的文章就介紹到這了,更多相關(guān)Android View事件分發(fā)機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android關(guān)于WebView中無(wú)法定位的問(wèn)題解決
本篇文章主要介紹了Android關(guān)于WebView中無(wú)法定位的問(wèn)題解決,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-10-10詳解 Kotlin Reference Basic Types, String, Array and Imports
這篇文章主要介紹了詳解 Kotlin Reference Basic Types, String, Array and Imports的相關(guān)資料,需要的朋友可以參考下2017-06-06Android 屬性動(dòng)畫ValueAnimator與插值器詳解
這篇文章主要介紹了Android 屬性動(dòng)畫ValueAnimator與插值器詳解的相關(guān)資料,需要的朋友可以參考下2017-05-05Android獲取手機(jī)通訊錄、sim卡聯(lián)系人及調(diào)用撥號(hào)界面方法
這篇文章主要介紹了Android獲取手機(jī)通訊錄、sim卡聯(lián)系人及調(diào)用撥號(hào)界面方法,本文分別給出實(shí)現(xiàn)代碼實(shí)現(xiàn)獲取通訊錄和sim卡的聯(lián)系人,以及權(quán)限配置和調(diào)用系統(tǒng)撥打電話的界面的實(shí)現(xiàn)代碼,需要的朋友可以參考下2015-04-04Android手勢(shì)ImageView三部曲 第三部
這篇文章主要為大家詳細(xì)介紹了Android手勢(shì)ImageView三部曲的第三部,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03Android使用系統(tǒng)自帶的相機(jī)實(shí)現(xiàn)一鍵拍照功能
這篇文章主要介紹了Android使用系統(tǒng)自帶的相機(jī)實(shí)現(xiàn)一鍵拍照功能,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下2017-01-01