Android 事件分發機制

Android 事件分發機制

[TOC]java

前言

Android 分發機制是每一個 Android 開發者所要必須瞭解的知識點,瞭解了分發機制之後就能夠很輕鬆的解決開發中遇到的問題,好比:微信

  • 實現鍵盤彈出時,點擊空白處隱藏鍵盤
  • 解決滑動衝突
  • 自定義 View 實現仿微信錄音
  • 還有一些其餘的應用等。

我會根據源碼的方式進行講解,儘可能描述的清楚些。app

Android 事件分發中的事件是什麼?

首先,咱們在操做移動設備的時候,大多數的操做都是經過手指在屏幕上點擊、滑動進行的,爲何咱們在點擊一個按鈕、滑動一個列表的時候可以操控頁面呢?ide

其實這裏就是 Android 事件分發機制的體現。源碼分析

在咱們點擊按鈕的時候,會有按下、擡起的操做,其實這兩個操做對應的就是 ACTION_DOWN 事件和 ACTION_UP 事件。佈局

在咱們滑動頁面的時候,會有按下、移動、移動。。。移動、擡起的操做,這這就對應事件分發裏面的 ACTION_DOWN 事件、ACTION_MOVE、ACTION_MOVE。。。ACTION_MOVE、 ACTION_UP 事件。post

不論是上面的點擊仍是滑動,在進行的時候,都是構成了一個事件序列,一個事件序列,通常都是從 ACTION_DOWN 開始、 ACTION_UP 結束的。動畫

在 Android 中,這些事件序列被封裝到了 MotionEvent 中。在 MotionEvent 中通常有如下幾種重要的事件:this

事件類型 觸發條件
MotionEvent.ACTION_DOWN 手指接觸到屏幕
MotionEvent.ACTION_MOVE 手指在屏幕上滑動
MotionEvent.ACTION_UP 手指離開屏幕
MotionEvent.ACTION_CANCEL 意外緣由致使事件序列終止

大致上,就是這四種不一樣的事件來構成事件分發裏面的事件序列。spa

Android 事件分發是什麼?

上面講了關於事件的概念,那麼事件究竟是怎麼分發的呢?

這要從咱們應用頁面講起。Android 中的頁面構成是這樣的:

-w346

其中 Activity 是在最外面的,而咱們平時 setContentView的佈局是在最裏面的,而咱們佈局正常狀況下是最外面是 ViewGroup ,裏面包裹了 ViewGroup 或者 View 。

好比在登陸頁面,咱們點擊了登陸按鈕,那麼就完成了一次完整的事件分發。

image

這個時候事件走向是這樣的

Activity -> PhoneWindow -> DecorView -> ViewGroup -> LinearLayout -> Button

能夠看到:事件是從最外面的 Activity 傳遞到最裏層的 Button 按鈕的。

其實事件分發就是將咱們手指產生的一些事件,傳遞到一個具體的 View 上去而且處理的過程。

爲何會有事件分發機制?

Android 上的View是樹形結構的,View 可能會重疊到一塊兒,當咱們點擊的時候,可能會有多個 View 進行響應,事件分發機制主要是解決事件該交給誰去處理的。

好比上面,咱們點擊了 Button,可是 Button 是在 LinearLayout 裏面的,那咱們點擊 Button 的時候,究竟是 交給 LinearLayout 處理仍是 Button 處理,由事件分發機制說了算。

還有就是 Android 中的滑動衝突等,都要須要依據事件分發機制去解決。

事件分發裏面重要的三個方法

  1. dispatchTouchEvent() 在 Activity 、 ViewGroup、 View 中都是有這個方法的,事件接收是從 Activity 的 dispatchTouchEvent() 開始的。
  2. onInterceptTouchEvent() 這個方法是 ViewGroup 獨有的,在 ViewGroup 中能夠經過這個方法肯定是否攔截該事件,若是攔截,那麼就由 ViewGroup 的 onTouchEvent() 方法接管事件序列。這個方法默認是返回 false 的,也就是默認不攔截事件
  3. onTouchEvent() 這個方法也是在 Activity 、 ViewGroup 、 View 中都有的,若是若是肯定在 Activity、ViewGroup、View 中處理事件,通常是在這個方法處理的。

事件分發講解

先看 Activity 的。

Activity 的事件分發

前面講到事件序列是從最外層的 Activity 開始接收的,而後依次分發到具體的 View 中的,那就先從最外層的 Activity 層開始看起,也就是從 Activity 的 dispatchTouchEvent() 方法看起。

public boolean dispatchTouchEvent(MotionEvent ev) {
    // 首先當咱們每次手指觸控屏幕的時候,都會去調用 onUserInteraction() 方法,
    // 若是你想知道用戶用某種方式和你正在運行的 activity 交互,能夠重寫此方法。
    // 由於在每次事件分發的時候都會調用到該方法。
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}
複製代碼

能夠看到會先調用 getWindow().superDispatchTouchEvent(ev) 方法,來看下這個 getWindow() 是什麼:

public Window getWindow() {
    return mWindow;
}
複製代碼

這裏返回的是一個 Window 對象,在 Android 裏面的 Window 是一個抽象類,惟一的實現是 PhoneWindow 類。也就是調用的 PhoneWindow 的 superDispatchTouchEvent(ev) 方法:

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    // 這裏調用了 mDecor 的 superDispatchTouchEvent 方法
    return mDecor.superDispatchTouchEvent(event);
}

// mDecor 是一個DecorView對象
private DecorView mDecor;

public boolean superDispatchTouchEvent(MotionEvent event) {
    // 調用父類的 super.dispatchTouchEvent 方法
    return super.dispatchTouchEvent(event);
}

// DecorView 繼承於 FrameLayout 
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {}

// FrameLayout 繼承於ViewGroup
public class FrameLayout extends ViewGroup {}
複製代碼

能夠看到,DecorView 是繼承於 FrameLayout 的,FrameLayout是繼承於 ViewGroup 的,因此最終調用了 ViewGroup 的 dispatchTouchEvent() 方法

這個時候,事件已經被傳遞到了 ViewGroup 。也就是說若是 getWindow().superDispatchTrackballEvent(ev) 這行代碼返回的是 true ,表示 事件被消費掉了,那麼本次事件分發就結束了。直接 return 。不會執行到

return onTouchEvent(ev);
複製代碼

也就是不會執行 Activity 的 onTouchEvent 方法,若是是getWindow().superDispatchTrackballEvent(ev) 返回的 false,那麼就表示ViewGroup 和 View 均沒有對事件進行處理,調用 Activity 的 onTouchEvent 方法。

上面已經講了 onInterceptTouchEvent() 是隻存在 ViewGroup 中的,上面的源碼也驗證了這一點。下面看看 ViewGroup 的 dispatchTouchEvent() 方法

ViewGroup 的事件分發。

ViewGroup 的事件分發,也是從 dispatchTouchEvent() 開始的,來看下關鍵代碼:

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
       // 省略n行代碼。。。
             // Handle an initial down.
      			// 這裏在每次事件是 MotionEvent.ACTION_DOWN 的時候,調用 resetTouchState()
      			// 由於事件序列是從 down 事件開始的,因此每次接收到 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();
            }
            // Check for interception.
            final boolean intercepted;
      			// mFirstTouchTarget 在子 View 接管事件的時候會賦值,不然爲 null
            // 若是是 ACTION_DOWN 事件,或者 mFirstTouchTarget 不爲空,代表ACTION_DOWN事件沒有被消費,
      			// 走 ViewGroup 的分發流程,
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                // 是否容許攔截 disallowIntercept = 是否禁用事件攔截的功能(默認是false),
              	// 可經過調用 requestDisallowInterceptTouchEvent()修改 FLAG_DISALLOW_INTERCEPT這個標誌位
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                // 若是不容許攔截,就會 執行 onInterceptTouchEvent(ev)
                if (!disallowIntercept) {
                    // 這裏會根據 onInterceptTouchEvent 的返回值判斷當前的 ViewGroup 是否進行攔截.
                  	// onInterceptTouchEvent(ev)的源碼顯示,默認是會返回 false 的,若是有須要咱們能夠返回 true
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    // ViewGroup 默認是不攔截的,因此置爲 false
                    intercepted = false;
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                // 若是不是 Down 事件,而且沒有子 View 接管事件,那麼 ViewGroup 會阻止後面的事件向後傳遞
              	// intercepted置爲 true ,攔截事件序列,不須要調用onInterceptTouchEvent(ev),本身處理
                intercepted = true;
            }

            // If intercepted, start normal event dispatch. Also if there is already
            // a view that is handling the gesture, do normal event dispatch.
            // 若是攔截,或者一個 View 接管了該事件序列,那麼就走正常的分發流程
            if (intercepted || mFirstTouchTarget != null) {
                ev.setTargetAccessibilityFocus(false);
            }
            
            // 若是沒有觸發取消事件而且沒有攔截
            if (!canceled && !intercepted) {
            
            //省略部分代碼...
             // for 循環遍歷 View 注意,這裏是倒敘的,也就是從最裏面的 View 進行遍歷
            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;
                        }
												// 檢查能不能接收到事件,檢查觸摸位置是否在View區域內,而且在不在播放動畫
              					// 若是都不知足,執行 continue 繼續循環下個 View
                        if (!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }
												// 看是否有 View 接管,若是有,跳出循環。
                        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);
              					// 這裏進行處理,若是返回 true ,那麼表示有 View 接管了 事件,那麼就給 newTouchTarget
              					// 在 addTouchTarget(child, idBitsToAssign)中賦值,View接管該事件。結束循環完成分發。
                        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;
                        }

                        // The accessibility focus didn't handle the event, so clear
                        // the flag and do a normal dispatch to all children.
                        ev.setTargetAccessibilityFocus(false);
                    }
            }            
            // 省略n行代碼。。。
    }
複製代碼

能夠看到,在執行 dispatchTouchEvent 的開始,若是是 ACTIOPN_DOWN 的話,調用 resetTouchState() 來重置全部的觸摸狀態,這裏會將 mFirstTouchTarget 設置爲 null 。而後準備新的週期,這樣作主要是由於事件序列是從 down 事件開始的,因此每次接收到 down 事件,就是一個新的事件序列。要從新開始處理。

而後執行

if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                // 是否容許攔截 disallowIntercept = 是否禁用事件攔截的功能(默認是false),
              	// 可經過調用 requestDisallowInterceptTouchEvent()修改 FLAG_DISALLOW_INTERCEPT這個標誌位
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                // 若是不容許攔截,就會 執行 onInterceptTouchEvent(ev)
                if (!disallowIntercept) {
                    // 這裏會根據 onInterceptTouchEvent 的返回值判斷當前的 ViewGroup 是否進行攔截.
                  	// onInterceptTouchEvent(ev)的源碼顯示,默認是會返回 false 的,若是有須要咱們能夠返回 true
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    // ViewGroup 默認是不攔截的,因此置爲 false
                    intercepted = false;
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                // 若是不是 Down 事件,而且沒有子 View 接管事件,那麼 ViewGroup 會阻止後面的事件向後傳遞
              	// intercepted置爲 true ,攔截事件序列,不須要調用onInterceptTouchEvent(ev),本身處理
                intercepted = true;
            }
複製代碼

這裏會判斷是否是 DOWN 事件或者是 mFirstTouchTarget 不是 null。mFirstTouchTarge != null 也就是已經找到可以接收 touch 事件的 View。這個時候會進入 if 內部。

在內部會先建立 disallowIntercept (禁止攔截) 標誌位。這個標誌位能夠在 子 view 中使用 requestDisallowInterceptTouchEvent(boolean disallowIntercept) 方法去設置。以便請求父 View 不攔截事件。

若是 disallowIntercept = false ,也就是在子 view 執行 requestDisallowInterceptTouchEvent(false)也就是請求攔截,那麼就會執行 onInterceptTouchEvent(ev); 方法去判斷當前的 ViewGroup 是否對該事件進行攔截。

public boolean onInterceptTouchEvent(MotionEvent ev) {
    if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
            && ev.getAction() == MotionEvent.ACTION_DOWN
            && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
            && isOnScrollbarThumb(ev.getX(), ev.getY())) {
        return true;
    }
    return false;
}
複製代碼

onInterceptTouchEvent 方法內部前面的是對一些特殊事件的處理,就先無論了,若是不知足上面的四個條件的話,就會直接返回 false ,也就是 ViewGroup 默認是不攔截的。因此這個時候的 intercepted 值爲 false 不攔截;

若是 disallowIntercept = false ,也就是在子 view 執行 requestDisallowInterceptTouchEvent(true)也就是請求不攔截,那麼ViewGroup 就不會去走 onInterceptTouchEvent 方法,直接將 intercepted 值置爲 false,表示不攔截。

咱們看到最後一個 else 把 intercepted 的值直接置爲 true,表示攔截,那麼是何時觸發的呢?

也就是說在 當前傳遞來的事件不是 DOWN 而且沒有 View 接管事件的時候,ViewGroup 默認是進行攔截的。很好理解,事件的開端沒有子 View 進行處理,那麼以後的事件也不會交給子 View 去處理了。

再日後的話就是經過一個循環,倒序遍歷全部的子 View,依次判斷能不能接收到事件或者是正在播放動畫,而後知足的話依次去執行 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))

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.
        // 若是 取消,或者 事件爲 ACTION_CANCEL,不作任何過濾或轉換
        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);
            }
            event.setAction(oldAction);
            return handled;
        }

        // Calculate the number of pointers to deliver.
        final int oldPointerIdBits = event.getPointerIdBits();
        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

        // If for some reason we ended up in an inconsistent state where it looks like we
        // might produce a motion event with no pointers in it, then drop the event.
        if (newPointerIdBits == 0) {
            return false;
        }

        // If the number of pointers is the same and we don't need to perform any fancy
        // irreversible transformations, then we can reuse the motion event for this
        // dispatch as long as we are careful to revert any changes we make.
        // Otherwise we need to make a copy.
        final MotionEvent transformedEvent;
        if (newPointerIdBits == oldPointerIdBits) {
            if (child == null || child.hasIdentityMatrix()) {
                if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
                    final float offsetX = mScrollX - child.mLeft;
                    final float offsetY = mScrollY - child.mTop;
                    event.offsetLocation(offsetX, offsetY);

                    handled = child.dispatchTouchEvent(event);

                    event.offsetLocation(-offsetX, -offsetY);
                }
                return handled;
            }
            transformedEvent = MotionEvent.obtain(event);
        } else {
            transformedEvent = event.split(newPointerIdBits);
        }

        // Perform any necessary transformations and dispatch.
        if (child == null) {
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            final float offsetX = mScrollX - child.mLeft;
            final float offsetY = mScrollY - child.mTop;
            transformedEvent.offsetLocation(offsetX, offsetY);
            if (!child.hasIdentityMatrix()) {
                transformedEvent.transform(child.getInverseMatrix());
            }
						// 若是有子 View ,那麼去調用子 View 的 dispatchTouchEvent 去處理該事件。也就
            //把事件傳遞到了下一級
            handled = child.dispatchTouchEvent(transformedEvent);
        }

        // Done.
        transformedEvent.recycle();
        return handled;
    }
複製代碼

在 dispatchTransformedTouchEvent 內部能夠看到,若是是 child 不爲 null 的時候,調用子 View 的 dispatchTouchEvent 去處理,這樣就把把事件交給子 View 去處理,須要注意的是:這個地方的返回值 handled 是 最後子 View 的 onTouchEvent 的返回值。

**若是返回值爲 true ,**那麼表示子 View 消費掉了事件,那麼就會在ViewGroup 的 dispatchTouchEvent 方法的內部經過:

newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;

private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
    final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
    target.next = mFirstTouchTarget;
    mFirstTouchTarget = target;
    return target;
}
複製代碼

對newTouchTarget 進行賦值。在 addTouchTarget 內部對 mFirstTouchTarget 進行賦值。

**若是返回的是 false,**那麼就跳過 if 內部。最終會執行到下面的代碼:

(這裏的代碼是 ViewGroup 的 dispatchTouchEvent(event) 方法的一部分)

// Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                // 沒有 View 處理,那麼交給本身處理,在 View 的 dispatchTouchEvent方法中調用
              	// onTouchEvent,執行 if (child == null) {
            	  // handled = super.dispatchTouchEvent(event);
					      // }
                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.
              	// 走到這裏說明了,已經把事件交給了具體的 View 處理,那麼會將MotionEvent.ACTION_DOWN 
              	// 的後續事件分發給mFirstTouchTarget指向的View去處理
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
	                  // 該TouchTarget已經在前面的狀況中被分發處理了,避免重複處理
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                      	// 若是被當前ViewGroup攔截,向下分發cancel事件
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                      	// dispatchTransformedTouchEvent()方法成功向下分發取消事件或分發正常事件
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                      	// 若是發送了取消事件,則移除分發記錄(鏈表移動操做)
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }
複製代碼

這裏會判斷有沒有 View 處理事件。

若是沒有 View 處理,就調用 dispatchTransformedTouchEvent 方法,傳入 child = null,執行 handled = super.dispatchTouchEvent(event);這個時候 ViewGroup super 的 dispatchTouchEvent方法,也就是調用 View 的 dispatchTouchEvent 去處理,進而在View 的 dispatchTouchEvent 中調用 onTouchEvent。這個時候調用的 onTouchEvent就是 ViewGroup 內部的 onTouchEvent 方法。

若是有 View 處理,那麼就把後續事件交給子 View 繼續處理。這個時候,事件就傳遞到了 View 裏面。

這裏有個點就是:

若是在子 View 中的 onTouchEvent 返回了 true,那麼 mFirstTouchTarget 就不爲空,就輪不到父 View 處理。

若是在子 View 中的 onTouchEvent 返回了 false,子 View 不處理事件,那麼 mFirstTouchTarget 就爲空,就須要父 View 處理。

View 的事件分發

其實 View 的分發機制就比較簡單了,View 的分發機制也是從 dispatchTouchEvent(event) 開始的:

public boolean dispatchTouchEvent(MotionEvent event) {
    // If the event should be handled by accessibility focus first.
    if (event.isTargetAccessibilityFocus()) {
        // We don't have focus or no virtual descendant has it, do not handle the event.
        if (!isAccessibilityFocusedViewOrHost()) {
            return false;
        }
        // We have focus and got the event, then use normal event dispatch.
        event.setTargetAccessibilityFocus(false);
    }

    boolean result = false;

    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(event, 0);
    }

    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // Defensive cleanup for new gesture
        stopNestedScroll();
    }

    if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
      	// 能夠看到,好比知足四個條件,纔會消費事件。
      	//一、ListenerInfo 不爲 null
        //二、mOnTouchListener 不爲 null
      	//三、View 的 enable 是可用的
      	//四、onTouch 方法返回的是 true
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
				// 從上面的代碼能夠看到,是先執行了 onTouch 方法的,若是在 onTouch 方法
      	// 裏面已經把事件消費掉,那麼久不會執行 onTouchEvent 方法,若是沒消費,
      	// 就判斷是否在 onTouchEvent 中消費掉,若是消費掉,也是直接返回。若是沒沒消費,繼續向下執行
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }

    if (!result && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }

    // Clean up after nested scrolls if this is the end of a gesture;
    // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
    // of the gesture.
    if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        stopNestedScroll();
    }

    return result;
}
複製代碼

再來看下 View 的 onTouchEvent :

public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;
    final int action = event.getAction();
		
		// 判斷view 是可點擊的
    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
            || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
		// 若是 View 可點擊,可是處於不可用狀態,仍然會消費掉事件。
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        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;
    }
  	// 若是 View 設置了代理,會調用代理的 onTouchEvent 方法
    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }
  
  				// 若是 View 是可用的,是可點擊的,而且沒有被 onTouchListener 的 onTouch 方法
  				// 消費事件,那麼就會執行到 點擊事件 performClick()
          if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                   ...
                                if (!post(mPerformClick)) {
                                    performClick();
                    ...
                    break;

            return true;
        }

        return false;
}
複製代碼

從上面的源碼分析能夠看出,View 的 onTouchListener 優先級高於 onTouchEvent 的優先級,onTouchEvent 的優先級高於 onClickListener onClick 的優先級。

而且從源碼中也看到了,若是咱們設置了 View 是 disabled 的,那麼也就沒有下面的 onTouch,由於 && 判斷的時候,前面爲 false,後面就不去判斷,因此 onTouch 就不會執行。可是不會影響執行 onTouchEvent。

在 onTouchEvent 的內部,若是獲得 View 是 disabled 的,就會返回 View 是否是 clickable 或者 longClickable 的,若是有一個是 true ,就消費掉該事件。不會執行onCLick 事件了。

onTouch 執行條件

  1. 父 ViewGroup 沒有攔截事件
  2. 當前 View 是可可用的

onClick 執行的條件以下:

  1. 父 ViewGroup 沒有攔截事件
  2. 當前 View 是可可用的
  3. 當前 View 是可點擊的
  4. 當前 View 內部對 ACTION_UP 事件處理
  5. onTouch不消費事件

爲了便於理解,能夠參考這張圖:

image

關於事件分發的總結

  1. 事件分發裏面的事件一般指 ACTION_DOWN、ACTION_MOVE、ACTION_UP、ACTION_Cancel 這四種,他們連在一塊兒就構成了一個時間序列。好比 ACTION_DOWN、ACTION_MOVE、ACTION_MOVE…ACTION_MOVE、ACTION_UP,這就構成了一個事件序列。
  2. 事件序列的傳遞是從 Activity 開始,依次通過、PhoneWindow、DecorView、ViewGroup、View。若是是最終的 View 也沒有處理的話,就依次向上移交,最終會在 Activity 的 onTouchEvent 方法中處理。
  3. 若是事件從 ViewGroup 中傳遞給 View 去處理的時候,若是 View 沒有處理掉,在 onTouchEvent 方法中返回了 false,那麼該事件就從新交給 ViewGroup 處理,而且後續的事件都不會再傳遞給該 View。
  4. onInterceptTouchEvent 方法只有在 ViewGroup 中存在,而且默認返回 false,表明 ViewGroup 不攔截事件。
  5. 正常狀況下,一個事件序列只能由一個 View 處理。若是一個 View 接管了事件,不論是具體的子 View仍是 ViewGroup,後續的事件都會讓這個 View 處理,除非人爲干預事件的分發過程。
  6. 子 View 能夠經過調用 requestDisallowInterceptTouchEvent(true) ,干預父元素的除了 ACTION_DOWN 事件之外的事件走向。 通常用於處理滑動衝突中,子控件請求父控件不攔截ACTION_DOWN之外的其餘事件,ACTION_DOWN事件不受影響。
  7. View 的 onTouchEvent 方法默認是返回 true 的,也就是會默認攔截事件。除非 它是不可點擊的,(clickable、longClickable 都爲 false)。View 的 longClickable 默認均爲 false,Button、ImageButton 的 clickable 默認爲 true,TextView clickable 默認爲false
  8. View 的 enable 屬性不會影響 onTouchEvent 的返回值,只要 clickable、longClickable 有一個爲 true,那麼onTouchEvent就默認會返回 true
  9. View 的點擊事件是在 ACTION_UP 事件處理的時候執行的,因此要執行,必需要有 ACTION_DOWN 和 ACTION_UP 兩個事件。

歡迎關注個人公衆號
相關文章
相關標籤/搜索