1. 程式人生 > >原始碼角度再看Android事件分發機制

原始碼角度再看Android事件分發機制

基礎瞭解

MotionEvent

所謂點選事件分發,其實就是對MotionEvent分發。當一個MotionEvent產生了以後,系統需要把這個事件傳遞給一個具體的View,而這個傳遞的過程就是分發過程。

三種主要事件Action


package android.view;

public final class MotionEvent extends InputEvent implements Parcelable {
  
	...

 	/**
     * Constant for {@link #getActionMasked}: A pressed gesture has started, the
     * motion contains the initial starting location.
     * <p>
     * This is also a good time to check the button state to distinguish
     * secondary and tertiary button clicks and handle them appropriately.
     * Use {@link #getButtonState} to retrieve the button state.
     * </p>
     * 手指剛接觸螢幕
     */
public static final int ACTION_DOWN = 0; /** * Constant for {@link #getActionMasked}: A pressed gesture has finished, the * motion contains the final release location as well as any intermediate * points since the last down or move event. * 手指在螢幕上移動 */ public
static final int ACTION_UP = 1; /** * Constant for {@link #getActionMasked}: A change has happened during a * press gesture (between {@link #ACTION_DOWN} and {@link #ACTION_UP}). * The motion contains the most recent point, as well as any intermediate * points since the last down or move event. * 手指從螢幕上鬆開的一瞬間 */
public static final int ACTION_MOVE = 2; ... }
  • 當手指點選屏幕後鬆開,事件序列為DOWN->UP
  • 當手指點選螢幕,滑動一會,再擡起手指離開螢幕,時間序列為DOWN->MOVE->…MOVE->UP

getX/getY | getRawX/getRawY

  • getX/getY 返回的是相當於當前View左上角的x和y座標
  • getRawX/getRawY 返回的是相對於手機螢幕左上角的x和y座標

三個方法瞭解一下

android.view.View # public boolean dispatchTouchEvent(MotionEvent event)

用來進行事件的分發。如果事件能夠傳遞給當前View,那麼此方法一定會被呼叫。

android.view.ViewGroup # public boolean onInterceptTouchEvent(MotionEvent ev)(MotionEvent ev)

在dispatchTouchEvent內部呼叫,用來判斷是否攔截某個事件,如果當前View攔截了某個事件,那麼同一個事件序列當中,此方法不會被再次呼叫,返回結果表示是否攔截當前事件**(true表示攔截,false表示繼續向下分發給它的子元素)**。

android.view.View # public boolean onTouchEvent(MotionEvent event)

還是在dispatchTouchEvent內部呼叫,用來處理點選事件,返回結果表示是否消耗當前事件,如果不消耗,則在同一個事件序列中,當前View無法再次接收到事件。

一段經典的虛擬碼:


    public boolean dispatchTouchEvent(MotionEvent ev){  
        boolean consume = false;  
        if(onInterceptTouchEvent(ev)){  
            consume = onTouchEvent(ev);  
        } else {  
            consume = child.dispatchTouchEvent(ev);  
        }  
          
        return consume;  
    }  

從上面的程式碼可以看出,對於一個根ViewGroup,點選事件產生後,首先會傳遞給它,這時它的dispatchTouchEvent被呼叫,如果這個ViewGroup的onInterceptTouchEvent執行後返回結果,並根據這個結果進行不同方式的處理: **a.**若返回結果為為true,則表示攔截,這時候根據程式碼顯示就是終止對事件的分發並且自身呼叫onTouchEvent對本此事件進行處理。 **b.**若返回結果為false,根據程式碼顯示,則會去呼叫child.dispatchTouchEvent(ev),也就是繼續向下分發給自己的子View元素,再往後就是子元素去進行處理直到這個事件結束。 _

一些結論

  • (1)同一個事件序列是指從手指接觸螢幕的那一刻起,到手指離開螢幕的那一刻結束,在這個過程中所產生的一系列事件,這個事件序列以down事件開始,中間含有數量不定的move事件,最終以up事件結束。

  • (2)正常情況下,一個事件序列只能被一個View攔截且消耗。這一條的原因可以參考(3),因為一旦一個元素攔截了某此事件,那麼同一個事件序列內的所有事件都會直接交給它處理,因此同一個事件序列中的事件不能分別由兩個View同時處理,但是通過特殊手段可以做到,比如一個View將本該自己處理的事件通過onTouchEvent強行傳遞給其他View處理。

  • (3)某個View一旦決定攔截,那麼這一個事件序列都只能由它來處理(如果事件序列能夠傳遞給它的話),並且它的onInterceptTouchEvent不會再被呼叫。這條也很好理解,就是說當一個View決定攔截一個事件後,那麼系統會把同一個事件序列內的其他方法都直接交給它來處理,因此就不用再呼叫這個View的onInterceptTouchEvent去詢問它是否要攔截了。

  • (4)某個View一旦開始處理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false),那麼同一事件序列中的其他事件都不會再交給它來處理,並且事件將重新交由它的父元素去處理,即父元素的onTouchEvent會被呼叫。意思就是事件一旦交給一個View處理,那麼它就必須消耗掉,否則同一事件序列中剩下的事件就不再交給它來處理了,這就好比上級交給程式設計師一件事,如果這件事沒有處理好,短期內上級就不敢再把事情交給這個程式設計師做了,二者是類似的道理。

  • (5)如果View不消耗除ACTION_DOWN以外的其他事件,那麼這個點選事件會消失,此時父元素的onTouchEvent並不會被呼叫,並且當前View可以持續收到後續的事件,最終這些消失的點選事件會傳遞給Activity處理。

  • (6)ViewGroup預設不攔截任何事件。Android原始碼中ViewGroup的onInterceptTouch-Event方法預設返回false。

  • (7)View沒有onInterceptTouchEvent方法,一旦有點選事件傳遞給它,那麼它的onTouchEvent方法就會被呼叫。

  • (8)View的onTouchEvent預設都會消耗事件(返回true),除非它是不可點選的(clickable 和longClickable同時為false)。View的longClickable屬性預設都為false,clickable屬性要分情況,比如Button的clickable屬性預設為true,而TextView的clickable屬性預設為false。

  • (9)View的enable屬性不影響onTouchEvent的預設返回值。哪怕一個View是disable狀態的,只要它的clickable或者longClickable有一個為true,那麼它的onTouchEvent就返回true。

  • (10)onClick會發生的前提是當前View是可點選的,並且它收到了down和up的事件。

  • (11)事件傳遞過程是由外向內的,即事件總是先傳遞給父元素,然後再由父元素分發給子View,通過requestDisallowInterceptTouchEvent方法可以在子元素中干預父元素的事件分發過程,但是ACTION_DOWN事件除外。

深入瞭解

事件分發流程梳理1—從Activity到頂級ViewGroup(View)

點選事件最先傳遞給Activity,由Activity的dispatchTouchEvent來進行事件派發,具體工作是由Window來完成。先知道這麼多,我們就以Activity作為入口,然後就邊看程式碼變分析吧。

Activity#dispatchTouchEvent


	package android.app;
	
	public class Activity extends ContextThemeWrapper
	        implements LayoutInflater.Factory2,
	        Window.Callback, KeyEvent.Callback,
	        OnCreateContextMenuListener, ComponentCallbacks2,
	        Window.OnWindowDismissedCallback, WindowControllerCallback,
	        AutofillManager.AutofillClient {
	
	   ......
	
	   public boolean dispatchTouchEvent(MotionEvent ev) {
	        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
	            onUserInteraction();
	        }
	        if (getWindow().superDispatchTouchEvent(ev)) {
	            return true;
	        }
	        return onTouchEvent(ev);
	    }
	
	   ......
	}


一個判斷,當事件DOWN來襲的時候。呼叫onUserInteraction(),首先它是一個空實現方法,這種方法一般是系統留給我們複寫的介面方法。然後它跟另外一個方法有關係onUserLeaveHint,這倆都是在特定情境下可以複寫的介面方法。

  • Activity#onUserInteraction() activity在分發各種事件的時候會呼叫該方法,注意:啟動另一個activity,Activity#onUserInteraction()會被呼叫兩次,一次是activity捕獲到事件,另一次是呼叫Activity#onUserLeaveHint()之前會呼叫Activity#onUserInteraction()。

  • Activity#onUserLeaveHint() 使用者手動離開當前activity,會呼叫該方法,比如使用者主動切換任務,短按home進入桌面等。系統自動切換activity不會呼叫此方法,如來電,滅屏等。

然後我們看到這一行:

   if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
   }

getWindow()返回一個Window,然後看Window下的superDispatchTouchEvent。

Window#superDispatchTouchEvent

package android.view;

/**
 * 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.
 *
 * <p>The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.
 */
public abstract class Window {

   ......

    /**
     * Used by custom windows, such as Dialog, to pass the touch screen event
     * further down the view hierarchy. Application developers should
     * not need to implement or call this.
     *
     */
     public abstract boolean superDispatchTouchEvent(MotionEvent event);
   ......
}

一個抽象方法,我們去找他的實現類。看頂上的註釋: The only existing implementation of this abstract class is android.view.PhoneWindow

PhoneWindow#superDispatchTouchEvent


package com.android.internal.policy

public class PhoneWindow extends Window implements MenuBuilder.Callback { 

 // This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;
    ......

    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

    ......

}

科普一波: DecorView是啥?它一般是當前介面最底層的容器(即setContentView之後產生的View的父容器)。可以通過程式碼Activity$View decorView = getWindow().getDecorView(); 獲得

繼續看,mDecor.superDispatchTouchEvent(event);看看DecorView嘛

PhoneWindow#superDispatchTouchEvent


package com.android.internal.policy

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {

 // This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;
    ......

     public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }

    ......

}

它呼叫了super.dispatchTouchEvent(event),看它的父類,這裡DecorView extends FrameLayout,FrameLayout是個ViewGroup,本質上也是個View,所以到這兒,事件就從Activity傳遞到了ViewGroup層。

事件分發流程梳理2-ViewGroup層的事件下發流程

分發邏輯回顧

點選事件達到頂級View(一般是一個ViewGroup)以後,會呼叫ViewGroup的dispatchTouchEvent方法,然後的邏輯是這樣的:

如果頂級ViewGroup攔截事件即onInterceptTouchEvent返回true,則事件由ViewGroup處理

  • 這時如果ViewGroup的mOnTouchListener被設定,則onTouch會被呼叫。
  • 否則onTouchEvent會被呼叫。在onTouchEvent中,如果設定了mOnClickListener,則onClick會被呼叫。
  • 如果都提供的話,onTouch會遮蔽掉onTouchEvent。

如果頂級ViewGroup不攔截事件,返回false

  • 則事件會傳遞給它所在的點選事件鏈上的子View,這時子View的dispatchTouchEvent會被呼叫。到此為止,事件已經從頂級View傳遞給了下一層View,接下來的傳遞過程和頂級View是一致的,如此迴圈,完成整個事件的分發。

ViewGroup—onInterceptTouchEvent呼叫流程分析(是否攔截事件)

在ViewGroup內搜到方法dispatchTouchEvent,然後ctrl+f // Check for interception.

   // 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;
   }
  • actionMasked == MotionEvent.ACTION_DOWN 當前事件是按下事件。
  • mFirstTouchTarget != null 當事件由ViewGroup的子元素處理成功時,mFirstTouchTarget會被賦值指向子元素,於是(ViewGroup沒攔截時)mFirstTouchTarget!=null,當ViewGroup攔截時,則mFirstTouchTarget==null
  • if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) 這層if的意思是:當摁下ACTION_DWON時,就不去考慮之前有沒有攔截過該事件,都具有攔截的資格(能夠呼叫onInterceptTouchEvent),噹噹前事件時UP或者MOVE時,則需要考慮之前有沒有攔截過事件,若沒攔截就還有機會去攔截該事件,若之前攔截過了,則不再考慮再次呼叫onInterceptTouchEvent方法,本次後續的時間序列都攔截
  • 當onInterceptTouchEvent不再考慮被呼叫時,那麼走到else裡邊,intercepted = true,那麼當這個intercepted為true時,看他註釋:
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.

我們主要看後一句,this view group continues to intercept touches,該ViewGroup繼續攔截觸控事件,意思就是後續的事件序列繼續攔截。也就是說intercepted=true表示當前事件序列攔截。

  • 假設當前具備呼叫OnInterceptTouchEvent方法的資格時,還有一層判斷:final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;,這層判斷是啥意思呢?它產出一個值disallowIntercept,用於判斷:
ViewGroup#dispatchTouchEvent

   if (!disallowIntercept) {
            intercepted = onInterceptTouchEvent(ev);
            ev.setAction(action); // restore action in case it was changed
   } else {
            intercepted = false;
   }
  • 這個標記位FLAG_DISALLOW_INTERCEPT是通過方法requestDisallowInterceptTouchEvent(boolean disallowIntercept)來設定的,當requestDisallowInterceptTouchEvent(true)時,if (!disallowIntercept)為false,那麼表示不攔截intercepted = false,這也是這個方法名字的由來,“請求不攔截觸控事件”。但是,有時候我們子View呼叫這個方法會失效,為什麼?因為,當面對事件ACTION_DOWN的時候這個標記會被重置,也就是resetTouchState這個方法,那麼requestDisallowInterceptTouchEvent(boolean disallowIntercept)這個方法豈不是很雞肋?這裡先TODO一下,等看到滑動衝突的時候再來看如何利用這個方法,原始碼:

重置FLAG_DISALLOW_INTERCEPT為false

ViewGroup#dispatchTouchEvent

	 // 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();
	}
 /**
     * Resets all touch state in preparation for a new cycle.
     */
    private void resetTouchState() {
        clearTouchTargets();
        resetCancelNextUpFlag(this);
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        mNestedScrollAxes = SCROLL_AXIS_NONE;
    }

FLAG_DISALLOW_INTERCEPT

ViewGroup#requestDisallowInterceptTouchEvent

   @Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {

        if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
            // We're already in this state, assume our ancestors are too
            return;
        }

        if (disallowIntercept) {
            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
        } else {
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }

        // Pass it up to our parent
        if (mParent != null) {
            mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
        }
    }

ViewGroup—DispatchTouchEvent呼叫流程分析(分發到View)

  • 遍歷childCount,ViewGroup的所有子元素。
  • 判斷子元素是否能夠接收到事件 a.座標是否在區域內 b.是否在實行動畫 這個判斷是個||的關係,這倆個條件有一個不成立時,則忽略接下來的邏輯直接進入下一次子元素迴圈(continue)
 if (!canViewReceivePointerEvents(child)
       || !isTransformedTouchPointInView(x, y, child, null)) {
        ev.setTargetAccessibilityFocus(false);
        continue;
  }
  • 若當前事件的座標在區域內,並且沒有在執行動畫,那麼就去呼叫dispatchformedTouchEvent**注意這裡dispatchformedTouchEvent()很重要,可以說ViewGroup內比較核心的一個方法,看下面的程式碼,如果child為空則呼叫super.dispatchTouchEvent,如果不為空則呼叫child.dispatchTouchEvent。那麼我可以猜測ViewGroup寫這個方法的目的是啥?因為一個MotionEvent的分發最終目的還是要被處理掉(onTouchEvent),child是ViewGroup時就遞迴執行ViewGroup的dispatchTouchEvent,直到是View時呼叫View的dispatchTouchEvent,而View的dispatchTouchEvent內有onTouchEvnet()的邏輯。ViewGroup內也有當intercept為true時呼叫super.onTouchEvent()方法的邏輯。這樣,就能保證事件最終總能有個地方被處理。** 另外,我們在ViewGroup內是找不到onTouchEvent方法的,但是在它的父類View裡可以找到,我們前面分析了當onIntercept方法攔截了事件之後,則會自己處理onTouchEvent(),這裡也是通過子類調父類的形式來呼叫的。
    /**
     * Transforms a motion event into the coordinate space of a particular child view,
     * filters out irrelevant pointer ids, and overrides its action if necessary.
     * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
     */
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        // Canceling motions is a special case.  We don't need to perform any transformations
        // or filtering.  The important part is the action, not the contents.
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);//實際上就是呼叫的子元素的dispatchTouchEvent
            }
            event.setAction(oldAction);
            return handled;
        }

     	  ......
        return handled;
    }

  • 如果子元素的dispatchTouchEvent方法返回true,那麼mFirstTouchTarget就會被賦值同時跳出(break)for迴圈,賦值過程真實發生在addTouchTarget, 注意這裡很重要,mFirstTouchTarget的賦值直接影響事件攔截邏輯,並且ViewGroup向View的事件分發的過渡點也在這個mFirstTouchTarget
   /**
     * Adds a touch target for specified child to the beginning of the list.
     * Assumes the target child is not already present.
     */
    private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }
  • 如果子元素的dispatchTouchEvent返回false,ViewGroup就會將事件分發給下一個子元素。(如果還有下一個子元素的話)

  • 最後附上ViewGroup的DispatchTouchEvnetn的程式碼整體概況,已做好註釋:


	ViewGroup#dispatchTouchEvent

 	  if (!canceled && !intercepted) {

           ......

            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;

                // Clean up earlier touch targets for this pointer id in case they
                // have become out of sync.
                 ......

                    final View[] children = mChildren;
					//遍歷childCount,ViewGroup的所有子元素
                    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;
                        }

						//判斷子元素是否能夠接收到事件 a.座標是否在區域內  b.是否在實行動畫
                        if (!canViewReceivePointerEvents(child)
                                || !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);

						//dispatchTransformedTouchEvent實際上就是呼叫的子元素的dispatchTouchEvent方法
                        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();

							//如果子元素的dispatchTouchEvent方法返回true,那麼mFirstTouchTarget就會被賦值同時跳出for迴圈,賦值過程真實發生在addTouchTarget
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }

						//如果子元素的dispatchTouchEvent返回false,ViewGroup就會將事件分發給下一個子元素。(如果還有下一個子元素的話)
                        // The accessibility focus didn't handle the event, so clear
                        // the flag and do a normal dispatch to all children.
						
                        ev.setTargetAccessibilityFocus(false);
                    }

                    if (preorderedList != null) preorderedList.clear();
                }

                ......
            }

     	}


            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                //No touch targets so treat this as an ordinary view.
				//mFirstTouchTarget == null,表示上面那一波遍歷並沒有子View正常的處理了分發下去的事件
				//a.包含倆種情況,ViewGroup沒有子View
				//b.子元素了處理了事件,但子View的DispatchTouchEvent返回了false(一般是由於子View的OnTouchEvent返回了false)
				//可以看到這裡第三個引數是null,當著引數為null的時候,該方法內回去調handled = super.dispatchTouchEvent(event);
				//由於View是ViewGroup的父類,所以就轉跳到了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.
            		   ......
                    target = next;
                }
            }

事件分發流程梳理3-View事件分發和處理流程

從ViewGroup的dispatchTransformTouchEvent方法傳遞到View這裡,事件已經到達View,可能是父元素分發給子元素接著子View自己呼叫自己,也可能是父元素自己處理(子類ViewGroup呼叫父類View)。

到了View這一層,重點不在於如何分發,而是在於消費機制與處理,重點方法onTouchEvent()。

dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent event) {
       ...
       boolean result = false;
       if (onFilterTouchEventForSecurity(event)) {
           //noinspection SimplifiableIfStatement
           ListenerInfo li = mListenerInfo;
           if (li != null && li.