<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                #### 3.4.2 事件分發的源碼解析 上一節分析了View的事件分發機制,本節將會從源碼的角度去進一步分析、證實上面的結論。 **1.Activity對點擊事件的分發過程** 點擊事件用MotionEvent來表示,當一個點擊操作發生時,事件最先傳遞給當前Activity,由Activity的dispatchTouchEvent來進行事件派發,具體的工作是由Activity內部的Window來完成的。Window會將事件傳遞給decor view, decor view一般就是當前界面的底層容器(即setContentView所設置的View的父容器),通過Activity.getWindow. getDecorView()可以獲得。我們先從Activity的dispatchTouchEvent開始分析。 源碼:Activity#dispatchTouchEvent public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); } 現在分析上面的代碼。首先事件開始交給Activity所附屬的Window進行分發,如果返回true,整個事件循環就結束了,返回false意味著事件沒人處理,所有View的onTouchEvent都返回了false,那么Activity的onTouchEvent就會被調用。 接下來看Window是如何將事件傳遞給ViewGroup的。通過源碼我們知道,Window是個抽象類,而Window的superDispatchTouchEvent方法也是個抽象方法,因此我們必須找到Window的實現類才行。 源碼:Window#superDispatchTouchEvent public abstract boolean superDispatchTouchEvent(MotionEvent event); 那么到底Window的實現類是什么呢?其實是PhoneWindow,這一點從Window的源碼中也可以看出來,在Window的說明中,有這么一段話: > Abstract base class for a top-level window look and behavior policy. An instance of this class should be used as the top-level view added to the window manager. It provides standard UI policies such as a background, title area, default key processing, etc. > The only existing implementation of this abstract class is android. policy.PhoneWindow, which you should instantiate when needing a Window. Eventually that class will be refactored and a factory method added for creating Window instances without knowing about a particular implementation. 上面這段話的大概意思是:Window類可以控制頂級View的外觀和行為策略,它的唯一實現位于android.policy.PhoneWindow中,當你要實例化這個Window類的時候,你并不知道它的細節,因為這個類會被重構,只有一個工廠方法可以使用。盡管這看起來有點模糊,不過我們可以看一下android.policy.PhoneWindow這個類,盡管實例化的時候此類會被重構,僅是重構而已,功能是類似的。 由于Window的唯一實現是PhoneWindow,因此接下來看一下PhoneWindow是如何處理點擊事件的,如下所示。 源碼:PhoneWindow#superDispatchTouchEvent public boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event); } 到這里邏輯就很清晰了,PhoneWindow將事件直接傳遞給了DecorView,這個DecorView是什么呢?請看下面: private final class DecorView extends FrameLayout implements RootViewSur- faceTaker // This is the top-level view of the window, containing the window decor. private DecorView mDecor; @Override public final View getDecorView() { if (mDecor == null) { installDecor(); } return mDecor; } 我們知道,通過((ViewGroup)getWindow().getDecorView().findViewById(android.R.id. content)).getChildAt(0)這種方式就可以獲取Activity所設置的View,這個mDecor顯然就是getWindow().getDecorView()返回的View,而我們通過setContentView設置的View是它的一個子View。目前事件傳遞到了DecorView這里,由于DecorView繼承自FrameLayout且是父View,所以最終事件會傳遞給View。換句話來說,事件肯定會傳遞到View,不然應用如何響應點擊事件呢?不過這不是我們的重點,重點是事件到了View以后應該如何傳遞,這對我們更有用。從這里開始,事件已經傳遞到頂級View了,即在Activity中通過setContentView所設置的View,另外頂級View也叫根View,頂級View一般來說都是ViewGroup。 **2.頂級View對點擊事件的分發過程** 關于點擊事件如何在View中進行分發,上一節已經做了詳細的介紹,這里再大致回顧一下。點擊事件達到頂級View(一般是一個ViewGroup)以后,會調用ViewGroup的dispatchTouchEvent方法,然后的邏輯是這樣的:如果頂級ViewGroup攔截事件即onInterceptTouchEvent返回true,則事件由ViewGroup處理,這時如果ViewGroup的mOnTouchListener被設置,則onTouch會被調用,否則onTouchEvent會被調用。也就是說,如果都提供的話,onTouch會屏蔽掉onTouchEvent。在onTouchEvent中,如果設置了mOnClickListener,則onClick會被調用。如果頂級ViewGroup不攔截事件,則事件會傳遞給它所在的點擊事件鏈上的子View,這時子View的dispatchTouchEvent會被調用。到此為止,事件已經從頂級View傳遞給了下一層View,接下來的傳遞過程和頂級View是一致的,如此循環,完成整個事件的分發。 首先看ViewGroup對點擊事件的分發過程,其主要實現在ViewGroup的dispatchTouch-Event方法中,這個方法比較長,這里分段說明。先看下面一段,很顯然,它描述的是當前View是否攔截點擊事情這個邏輯。 // Check for interception. final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || 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. intercepted = true; } 從上面代碼我們可以看出,ViewGroup在如下兩種情況下會判斷是否要攔截當前事件:事件類型為ACTION_DOWN或者mFirstTouchTarget ! = null。ACTION_DOWN事件好理解,那么mFirstTouchTarget ! = null是什么意思呢?這個從后面的代碼邏輯可以看出來,當事件由ViewGroup的子元素成功處理時,mFirstTouchTarget會被賦值并指向子元素,換種方式來說,當ViewGroup不攔截事件并將事件交由子元素處理時mFirstTouchTarget ! = null。反過來,一旦事件由當前ViewGroup攔截時,mFirstTouchTarget ! = null就不成立。那么當ACTION_MOVE和ACTION_UP事件到來時,由于(actionMasked == MotionEvent. ACTION_DOWN || mFirstTouchTarget ! = null)這個條件為false,將導致ViewGroup的onInterceptTouchEvent不會再被調用,并且同一序列中的其他事件都會默認交給它處理。 當然,這里有一種特殊情況,那就是FLAG_DISALLOW_INTERCEPT標記位,這個標記位是通過requestDisallowInterceptTouchEvent方法來設置的,一般用于子View中。FLAG_DISALLOW_INTERCEPT一旦設置后,ViewGroup將無法攔截除了ACTION_DOWN以外的其他點擊事件。為什么說是除了ACTION_DOWN以外的其他事件呢?這是因為ViewGroup在分發事件時,如果是ACTION_DOWN就會重置FLAG_DISALLOW_INTERCEPT這個標記位,將導致子View中設置的這個標記位無效。因此,當面對ACTION_DOWN事件時,ViewGroup總是會調用自己的onInterceptTouchEvent方法來詢問自己是否要攔截事件,這一點從源碼中也可以看出來。在下面的代碼中,ViewGroup會在ACTION_DOWN事件到來時做重置狀態的操作,而在resetTouchState方法中會對FLAG_DISALLOW_INTERCEPT進行重置,因此子View調用request- DisallowInterceptTouchEvent方法并不能影響ViewGroup對ACTION_DOWN事件的處理。 // 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(); } 從上面的源碼分析,我們可以得出結論:當ViewGroup決定攔截事件后,那么后續的點擊事件將會默認交給它處理并且不再調用它的onInterceptTouchEvent方法,這證實了3.4.1節末尾處的第3條結論。FLAG_DISALLOW_INTERCEPT這個標志的作用是讓ViewGroup不再攔截事件,當然前提是ViewGroup不攔截ACTION_DOWN事件,這證實了3.4.1節末尾處的第11條結論。那么這段分析對我們有什么價值呢?總結起來有兩點:第一點,onInterceptTouchEvent不是每次事件都會被調用的,如果我們想提前處理所有的點擊事件,要選擇dispatchTouchEvent方法,只有這個方法能確保每次都會調用,當然前提是事件能夠傳遞到當前的ViewGroup;另外一點,FLAG_DISALLOW_INTERCEPT標記位的作用給我們提供了一個思路,當面對滑動沖突時,我們可以是不是考慮用這種方法去解決問題?關于滑動沖突,將在3.5節進行詳細分析。 接著再看當ViewGroup不攔截事件的時候,事件會向下分發交由它的子View進行處理,這段源碼如下所示。 final View[] children = mChildren; for (int i = childrenCount -1; i >= 0; i--) { final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; final View child = (preorderedList == null) ? children[childIndex] : preorderedList.get(childIndex); if (! canViewReceivePointerEvents(child) || ! isTransformedTouchPointInView(x, y, child, null)) { 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的所有子元素,然后判斷子元素是否能夠接收到點擊事件。是否能夠接收點擊事件主要由兩點來衡量:子元素是否在播動畫和點擊事件的坐標是否落在子元素的區域內。如果某個子元素滿足這兩個條件,那么事件就會傳遞給它來處理。可以看到,dispatchTransformedTouchEvent實際上調用的就是子元素的dispatchTouchEvent方法,在它的內部有如下一段內容,而在上面的代碼中child傳遞的不是null,因此它會直接調用子元素的dispatchTouchEvent方法,這樣事件就交由子元素處理了,從而完成了一輪事件分發。 if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } 如果子元素的dispatchTouchEvent返回true,這時我們暫時不用考慮事件在子元素內部是怎么分發的,那么mFirstTouchTarget就會被賦值同時跳出for循環,如下所示。 newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; 這幾行代碼完成了mFirstTouchTarget的賦值并終止對子元素的遍歷。如果子元素的dispatchTouchEvent返回false, ViewGroup就會把事件分發給下一個子元素(如果還有下一個子元素的話)。 其實mFirstTouchTarget真正的賦值過程是在addTouchTarget內部完成的,從下面的addTouchTarget方法的內部結構可以看出,mFirstTouchTarget其實是一種單鏈表結構。mFirstTouchTarget是否被賦值,將直接影響到ViewGroup對事件的攔截策略,如果mFirstTouchTarget為null,那么ViewGroup就默認攔截接下來同一序列中所有的點擊事件,這一點在前面已經做了分析。 private TouchTarget addTouchTarget(View child, int pointerIdBits) { TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; mFirstTouchTarget = target; return target; } 如果遍歷所有的子元素后事件都沒有被合適地處理,這包含兩種情況:第一種是ViewGroup沒有子元素;第二種是子元素處理了點擊事件,但是在dispatchTouchEvent中返回了false,這一般是因為子元素在onTouchEvent中返回了false。在這兩種情況下,ViewGroup會自己處理點擊事件,這里就證實了3.4.1節中的第4條結論,代碼如下所示。 // Dispatch to touch targets. if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } 注意上面這段代碼,這里第三個參數child為null,從前面的分析可以知道,它會調用super.dispatchTouchEvent(event),很顯然,這里就轉到了View的dispatchTouchEvent方法,即點擊事件開始交由View來處理,請看下面的分析。 **3.View對點擊事件的處理過程** View對點擊事件的處理過程稍微簡單一些,注意這里的View不包含ViewGroup。先看它的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對點擊事件的處理過程就比較簡單了,因為View(這里不包含ViewGroup)是一個單獨的元素,它沒有子元素因此無法向下傳遞事件,所以它只能自己處理事件。從上面的源碼可以看出View對點擊事件的處理過程,首先會判斷有沒有設置OnTouchListener,如果OnTouchListener中的onTouch方法返回true,那么onTouchEvent就不會被調用,可見OnTouchListener的優先級高于onTouchEvent,這樣做的好處是方便在外界處理點擊事件。 接著再分析onTouchEvent的實現。先看當View處于不可用狀態下點擊事件的處理過程,如下所示。很顯然,不可用狀態下的View照樣會消耗點擊事件,盡管它看起來不可用。 if ((viewFlags & ENABLED_MASK) == DISABLED) { if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) ! = 0) { setPressed(false); } // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); } 接著,如果View設置有代理,那么還會執行TouchDelegate的onTouchEvent方法,這個onTouchEvent的工作機制看起來和OnTouchListener類似,這里不深入研究了。 if (mTouchDelegate ! = null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } 下面再看一下onTouchEvent中對點擊事件的具體處理,如下所示。 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) { mPerformClick = new PerformClick(); } if (! post(mPerformClick)) { performClick(); } } } ... } break; } ... return true; } 從上面的代碼來看,只要View的CLICKABLE和LONG_CLICKABLE有一個為true,那么它就會消耗這個事件,即onTouchEvent方法返回true,不管它是不是DISABLE狀態,這就證實了3.4.1節末尾處的第8、第9和第10條結論。然后就是當ACTION_UP事件發生時,會觸發performClick方法,如果View設置了OnClickListener,那么performClick方法內部會調用它的onClick方法,如下所示。 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; } View的LONG_CLICKABLE屬性默認為false,而CLICKABLE屬性是否為false和具體的View有關,確切來說是可點擊的View其CLICKABLE為true,不可點擊的View其CLICKABLE為false,比如Button是可點擊的,TextView是不可點擊的。通過setClickable和setLongClickable可以分別改變View的CLICKABLE和LONG_CLICKABLE屬性。另外,setOnClickListener會自動將View的CLICKABLE設為true, setOnLongClickListener則會自動將View的LONG_CLICKABLE設為true,這一點從源碼中可以看出來,如下所示。 public void setOnClickListener(OnClickListener l) { if (! isClickable()) { setClickable(true); } getListenerInfo().mOnClickListener = l; } public void setOnLongClickListener(OnLongClickListener l) { if (! isLongClickable()) { setLongClickable(true); } getListenerInfo().mOnLongClickListener = l; } 到這里,點擊事件的分發機制的源碼實現已經分析完了,結合3.4.1節中的理論分析和相關結論,讀者就可以更好地理解事件分發了。在3.5節將介紹滑動沖突相關的知識,具體情況請看下面的分析。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看