Android Touch事件分發(fā)深入了解
本文帶著大家深入學(xué)習(xí)觸摸事件的分發(fā),具體內(nèi)容如下
1. 觸摸動(dòng)作及事件序列
(1)觸摸事件的動(dòng)作
觸摸動(dòng)作一共有三種:ACTION_DOWN、ACTION_MOVE、ACTION_UP。當(dāng)用戶手指接觸屏幕時(shí),便產(chǎn)生一個(gè)動(dòng)作為ACTION_DOWN的觸摸事件,此時(shí)若用戶的手指立即離開屏幕,會(huì)產(chǎn)生一個(gè)動(dòng)作為ACTION_UP的觸摸事件;若用戶手指接觸屏幕后繼續(xù)滑動(dòng),當(dāng)滑動(dòng)距離超過了系統(tǒng)中預(yù)定義的距離常數(shù),則產(chǎn)生一個(gè)動(dòng)作為ACTION_MOVE的觸摸事件,系統(tǒng)中預(yù)定義的用來判斷用戶手指在屏幕上的滑動(dòng)是否是一個(gè)ACTION_MOVE動(dòng)作的這個(gè)距離常量叫做TouchSlop,可通過ViewConfiguration.get(getContext()).getScaledTouchSlop()獲取。
(2)事件序列
當(dāng)用戶的手指接觸屏幕,在屏幕上滑動(dòng),又離開屏幕,這個(gè)過程會(huì)產(chǎn)生一系列觸摸事件:ACTION_DOWN-->若干個(gè)ACTION_MOVE-->ACTION_UP。這一系列觸摸事件即為一個(gè)事件序列。
2. 觸摸事件的分發(fā)
(1)概述
當(dāng)產(chǎn)生了一個(gè)觸摸時(shí)間后,系統(tǒng)要負(fù)責(zé)把這個(gè)觸摸事件給一個(gè)View(TargetView)來處理,touch事件傳遞到TargetView的過程即為touch事件的分發(fā)。
觸摸事件的分發(fā)順序:Activity-->頂級View-->頂級View的子View-->. . .-->Target View
觸摸事件的響應(yīng)順序:TargetView --> TargetView的父容器 --> . . . -->頂級View -->Activity
(2)toush事件分發(fā)的具體過程
a. Activity對touch事件的分發(fā)
當(dāng)用戶手指接觸屏幕時(shí),便產(chǎn)生了一個(gè)touch事件,封裝了touch事件的MotionEvent最先被傳遞給當(dāng)前Activity,Activity的dispatchTouchEvent方法負(fù)責(zé)touch事件的分發(fā)。分發(fā)touch事件的實(shí)際工作由當(dāng)前Activity的Window完成,而Window會(huì)將touch事件傳遞給DecorView(當(dāng)前用戶界面頂級View)。Activity的dispatchTouchEvent方法代碼如下:
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }
根據(jù)以上代碼可以知道,touch事件會(huì)交由Window的superDispatchTouchEvent進(jìn)行分發(fā),若這個(gè)方法返回true,意味touch事件的分發(fā)過程結(jié)束,返回false則說明經(jīng)過層層分發(fā),沒有子View對這個(gè)事件進(jìn)行處理,即所有子View的onTouchEvent方法都返回false(即這個(gè)touch事件沒有被“消耗”)。這時(shí)會(huì)調(diào)用Activity的onTouchEvent方法來處理這個(gè)touch事件。
在Window的superDispatchTouchEvent方法中,首先會(huì)把touch事件分發(fā)給DecorView,因?yàn)樗钱?dāng)前用戶界面的頂級View。Window的superDispatchTouchEvent方法如下:
public abstract boolean superDispatchTouchEvent(MotionEvent ev);
是個(gè)抽象方法,這個(gè)方法由Window的實(shí)現(xiàn)類PhoneWindow實(shí)現(xiàn),PhoneWindow的superDispatchTouchEvent方法的代碼如下:
public boolean superDispatchTouchEvent(MotionEvent ev) { return mDecor.superDispatchTouchEvent(event); }
由以上代碼可得,PhoneWindow的superDispatchTouchEvent方法實(shí)際上是通過DecorView的superDispatchTouchEvent方法來完成自己的工作,也就是說,當(dāng)前Activity的Window直接將這個(gè)touch事件傳遞給了DecorView。也就是說,目前touch事件已經(jīng)經(jīng)過了如下的分發(fā):Activity-->Window-->DecorView。
b. 頂級View對touch事件的分發(fā)
經(jīng)過Activity與Window的分發(fā),現(xiàn)在touch事件已經(jīng)被傳遞到了DecorView的dispatchTouchEvent方法中。DecorView本質(zhì)上是一個(gè)ViewGroup(更具體的說是FrameLayout),ViewGroup的dispatchTouchEvent方法所做的工作可以分為如下幾個(gè)階段,第一個(gè)階段的主要代碼如下:
//Handle an initial down. if (actionMasked == MotionEvent.ACTION_DOWN) { //Throw away all previous state when starting a new touch gesture. //The framework may have dropped the up or cancel event for the previous gesture due to an app switch, ANR, or some other state change. cancelAndClearTouchTargets(ev); resetTouchState(); }
第一階段的主要工作有倆:一是在第6行的resetTouchState方法中完成了對FLAG_DISALLOW_INTERCEPT標(biāo)記的重置;二是第5行的cancelAndClearTouchTargets方法會(huì)清除當(dāng)前MotionEvent的touch target。關(guān)于FLAG_DISALLOW_INTERCEPT標(biāo)記和touch target,在下文會(huì)有相關(guān)說明。
第二階段的主要工作是決定當(dāng)前ViewGroup是否攔截本次的touch事件,主要代碼如下:
//Check for interception. final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWM || 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. intercept =true; }
由以上代碼我們可以知道,當(dāng)一個(gè)touch事件被傳遞到ViewGroup時(shí),會(huì)先判斷這個(gè)touch事件的動(dòng)作是否是ACTION_DOWN,如果這個(gè)事件是ACTION_DOWN或者mFirstTouchTarget不為null,就會(huì)根據(jù)FLAG_DISALLOW_INTERCEPT標(biāo)記決定是否攔截這個(gè)touch事件。那么mFirstTouchTarget是什么呢?當(dāng)touch事件被ViewGroup的子View成功處理時(shí),mFirstTouchTarget就會(huì)被賦值為成功處理touch事件的View,也就是上面提高的touch target。
總結(jié)一下上述代碼的流程:在子View不干預(yù)ViewGroup的攔截的情況下(上述代碼中的disallowIntercept為false),若當(dāng)前事件為ACTION_DOWN或者mFirstTouchTarget不為空,則會(huì)調(diào)用ViewGroup的onInterceptTouchEvent方法來決定最終是否攔截此事件;否則(沒有TargetView并且此事件不是ACTION_DOWN),當(dāng)前ViewGroup就攔截下此事件。 一旦ViewGroup攔截了某次touch事件,那么mFirstTouchTarget就不會(huì)被賦值,因此當(dāng)再有ACTION_MOVE或是ACTION_UP傳遞到該ViewGroup時(shí),mTouchTarget就為null,所以上述代碼第3行的條件就為false,ViewGroup會(huì)攔截下來。由此可得到的結(jié)論是:一旦ViewGroup攔截了某次事件,則同一事件序列中的剩余事件也會(huì)它默認(rèn)被攔截而不會(huì)再詢問是否攔截(即不會(huì)再調(diào)用onInterceptTouchEvent)。
這里存在一種特殊情形,就是子View通過requestDisallowInterceptTouchEvent方法設(shè)置父容器的FLAG_DISALLOW_INTERCEPT為true,這個(gè)標(biāo)記指示是否不允許父容器攔截,為true表示不允許。這樣做能夠禁止父容器攔截除ACTION_DOWN以外的所有touch事件。之所以不能夠攔截ACTION_DOWN事件,是因?yàn)槊慨?dāng)ACTION_DOWN事件到來時(shí),都會(huì)重置FLAG_DISALLOW_INTERCEPT這個(gè)標(biāo)記位為默認(rèn)值(false),所以每當(dāng)開始一個(gè)新touch事件序列(即到來一個(gè)ACTION_DOWN動(dòng)作),都會(huì)通過調(diào)用onInterceptTouchEven詢問ViewGroup是否攔截此事件。當(dāng)ACTION_DOWN事件到來時(shí),重置標(biāo)記位的工作是在上面的第一階段完成的。
接下來,會(huì)進(jìn)入第三階段的工作:
final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; if (!canceled && !intercepted) { // 不是ACTION_CANCEL并且不攔截 if (actionMasked == MotionEvent.ACTION_DOWN) { // 若當(dāng)前事件為ACTION_DOWN則去尋找這次事件新出現(xiàn)的touch target final int actionIndex = ev.getActionIndex(); // always 0 for down ... final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { // 根據(jù)觸摸的坐標(biāo)尋找能夠接收這個(gè)事件的touch target final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); final View[] children = mChildren; // 遍歷所有子View for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = i; final View child = children[childIndex]; // 尋找可接收這個(gè)事件并且touch事件坐標(biāo)在其區(qū)域內(nèi)的子View if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { continue; } newTouchTarget = getTouchTarget(child); // 找到了符合條件的子View,賦值給newTouchTarget if (newTouchTarget != null) { //Child is already receiving touch within its bounds. //Give it the new pointer in addition to ones it is handling. newTouchTarget.pointerIdBits |= idBitsToAssign; break; } resetCancelNextUpFlag(child); // 把ACTION_DOWN事件傳遞給子組件進(jìn)行處理 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(); //把mFirstTouchTarget賦值為newTouchTarget,此子View成為新的touch事件的起點(diǎn) newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } } } } }
當(dāng)ViewGroup不攔截本次事件,則touch事件會(huì)分發(fā)給它的子View進(jìn)行處理,相關(guān)代碼從第21行開始:遍歷所有ViewGroup的子View,尋找能夠處理此touch事件的子View,若一個(gè)子View不在播放動(dòng)畫并且touch事件坐標(biāo)位于其區(qū)域內(nèi),則該子View能夠處理此touch事件,并且會(huì)把該子View賦值給newTouchTarget。
若當(dāng)前遍歷到的子View能夠處理此touch事件,就會(huì)進(jìn)入第38行的dispatchTransformedTouchEvent方法,該方法實(shí)際上調(diào)用了子View的dispatchTouchEvent方法。dispatchTransformedTouchEvent方法中相關(guān)的代碼如下:
if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); }
若dispatchTransformedTouchEvent方法傳入的child參數(shù)不為null,則會(huì)調(diào)用child(即處理touch事件的子View)的dispatchTouchEvent方法。若該子View的dispatchTouchEvent方法返回true,則dispatchTransformedTouchEvent方法也會(huì)返回true,則表示成功找到了一個(gè)處理該事件的touch target,會(huì)在第55行把newTouchTarget賦值給mFirstTouchTarget(這一賦值過程是在addTouchTarget方法內(nèi)部完成的),并跳出對子View遍歷的循環(huán)。若子View的dispatchTouchEvent方法返回false,ViewGroup就會(huì)把事件分發(fā)給下一個(gè)子View。
若遍歷了所有子View后,touch事件都沒被處理(該ViewGroup沒有子View或是所有子View的dispatchTouchEvent返回false),ViewGroup會(huì)自己處理touch事件,相關(guān)代碼如下:
if (mFirstTouchTarget == null) { handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); }
由以上代碼可知,ViewGroup自己處理touch事件時(shí),會(huì)調(diào)用dispatchTransformedTouchEvent方法,傳入的child參數(shù)為null。根據(jù)上文的分析,傳入的chid為null時(shí),會(huì)調(diào)用super.dispatchTouchEvent方法,即調(diào)用View類的dispatchTouchEvent方法。
c. View對touch事件的處理
View的dispatchTouchEvent方法的主要代碼如下:
public boolean dispatchTouchEvent(MotionEvent event) { boolean result = false; . . . 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; } . . . return result; }
由上述代碼可知,View對touch事件的處理過程如下:由于View不包含子元素,所以它只能自己處理事件。它首先會(huì)判斷是否設(shè)置了OnTouchListener,若設(shè)置了,會(huì)調(diào)用onTouch方法,若onTouch方法返回true(表示該touch事件已經(jīng)被消耗),則不會(huì)再調(diào)用onTouchEvent方法;若onTouch方法返回false或沒有設(shè)置OnTouchListener,則會(huì)調(diào)用onTouchEvent方法,onTouchEvent對touch事件進(jìn)行具體處理的相關(guān)代碼如下:
if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { switch (event.getAction()) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { . . . if (!mHasPerformedLongPress) { //This is a tap, so remove the longpress check 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) { mPerformClck = new PeformClick(); } if (!post(mPerformClick)) { performClick(); } } } . . . } break; } . . . return true; }
由以上代碼可知,只要View的CLICKABLE屬性和LONG_CLICKABLE屬性有一個(gè)為true(View的CLICKABLE屬性和具體View有關(guān),LONG_CLICKABLE屬性默認(rèn)為false,setOnClikListener和setOnLongClickListener會(huì)分別自動(dòng)將以上倆屬性設(shè)為true),那么這個(gè)View就會(huì)消耗這個(gè)touch事件,即使這個(gè)View處于DISABLED狀態(tài)。若當(dāng)前事件是ACTION_UP,還會(huì)調(diào)用performClick方法,該View若設(shè)置了OnClickListener,則performClick方法會(huì)在其內(nèi)部調(diào)用onClick方法。performClick方法代碼如下:
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; } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); return result; }
以上是我學(xué)習(xí)Android中觸摸事件分發(fā)后的簡單總結(jié),很多地方敘述的還不夠清晰準(zhǔn)確
- Android事件分發(fā)機(jī)制的詳解
- Android View事件分發(fā)和消費(fèi)源碼簡單理解
- Android View的事件分發(fā)機(jī)制
- 談?wù)剬ndroid View事件分發(fā)機(jī)制的理解
- Android事件分發(fā)機(jī)制(下) View的事件處理
- Android事件分發(fā)機(jī)制(上) ViewGroup的事件分發(fā)
- Android View事件分發(fā)機(jī)制詳解
- Android View 事件分發(fā)機(jī)制詳解
- Android 事件分發(fā)詳解及示例代碼
- android事件分發(fā)機(jī)制的實(shí)現(xiàn)原理
相關(guān)文章
Android Jetpack系列之App Startup使用詳解
這篇文章主要為大家介紹了Android Jetpack系列之App Startup使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10Android進(jìn)程通信之Messenger和AIDL使用詳解
本篇文章主要介紹了Android進(jìn)程通信之Messenger和AIDL使用詳解,具有一定的參考價(jià)值,有興趣的可以了解一下。2017-01-01Android 使用AsyncTask實(shí)現(xiàn)多任務(wù)多線程斷點(diǎn)續(xù)傳下載
這篇文章主要介紹了Android 使用AsyncTask實(shí)現(xiàn)多任務(wù)多線程斷點(diǎn)續(xù)傳下載的相關(guān)資料,需要的朋友可以參考下2018-05-05Android判斷當(dāng)前App是在前臺(tái)還是在后臺(tái)
這篇文章主要為大家詳細(xì)介紹了Android判斷當(dāng)前App是在前臺(tái)還是在后臺(tái)的方法,感興趣的小伙伴們可以參考一下2016-08-08Android編程之九宮格實(shí)現(xiàn)方法實(shí)例分析
這篇文章主要介紹了Android編程之九宮格實(shí)現(xiàn)方法,結(jié)合實(shí)例形式分析了Android九宮格的實(shí)現(xiàn)方法與具體步驟,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2016-01-01Android使用socket創(chuàng)建簡單TCP連接的方法
這篇文章主要介紹了Android使用socket創(chuàng)建簡單TCP連接的方法,結(jié)合實(shí)例形式詳細(xì)分析了Android使用socket創(chuàng)建TCP連接的具體步驟與實(shí)現(xiàn)技巧,需要的朋友可以參考下2016-04-04