---
# System prepended metadata

title: Android View 事件分發 / 滑動衝突
tags: [Android Framework]

---

---
title: 'View 事件分發 / 滑動衝突'
disqus: kyleAlien
---

View 事件分發 / 滑動衝突
===

## Overview of Content


:::success
* 如果喜歡讀更好看一點的網頁版本，可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/)
    
    本篇文章對應的是 [**Android View 事件分發：處理滑動衝突 | 內部、外部事件攔截**](https://devtechascendancy.com/android-view-event_conflict-interception/)
:::


* 以下的 Android 源碼會採用 [**Android-10**](https://cs.android.com/android/platform/superproject/+/android-10.0.0_r1:frameworks/base/core/java/android/view/View.java) 的源碼


[TOC]

## 點擊事件傳入

點擊事件是從 **Activity(起點) 透過一層層傳遞至 View(終點) 中**，下圖是一個示意圖，至於若對建構 View 有興趣可以參考 [**LayoutInflater 分析**](https://hackmd.io/-2Uta0yBSbyhNayaev5eyg#LayoutInflater-%E5%8A%A0%E8%BC%89)

最終傳入 View 中讓其處理點擊事件 (從最外部 Acitivty -> Window -> View)
> ![](https://i.imgur.com/FbmUH6H.png)


### 事件分發概念

* 由於 View 的分發實作細節比較多，但我們要關注的 "**分發**"，主要是由 3 個方法來完成

    | 分發重點方法 | 功能 | 返回意義 |
    | -------- | -------- | -------- |
    | dispatchTouchEvent : boolean | 由上層 View 被觸發，傳遞至目標 View | 返回結果會由 `onTouchEvent`、子 View 的 `dispatchTouchEvent` 影響 (true: 被處理)  |
    | onInterceptTouchEvent : boolean | 在當前 View 中，用來判斷是否攔截某個事件 | 返回結果表示該事件是否被攔截 (true: 被攔截) |
    | onTouchEvent : boolean | 當前 View 已經攔截，開始處理是建 | 返回結果代表該事件是否被消耗 (true: 被處理) |

* 以下是 View 事件分發的偽程式，可以很好的描述出 View 事件分發概念
    ```java=
    // View 偽程式

    public boolean dispatchTouchEvent(MotionEvent e) {
        bool isEventConsume = false;

        if(onInterceptTouchEvent(e)) {        // 是否攔截分發 
            isEventConsume = onTouchEvent(e);
        } else {
            isEventConsume = child.dispatchTouchEvent(e);
        }

        return isEventConsume;
    }
    ```
    1. 可以看出分發順序 `dispatchTouchEvent` -> `onInterceptTouchEvent`，在依照是否消耗來決定之後的走向
    
    2. `dispatchTouchEvent` 的遞迴調用（若事件沒有被消耗，就會往子成員 `child` 繼續呼叫），直到找到消耗事件的 View
    
        :::info
        Android View 是一個 [**組件模式**](https://devtechascendancy.com/object-oriented_programming_composite/)、[**裝飾模式**](https://devtechascendancy.com/object-oriented_design_decorate/) 的混合設計模式，它擁有加強或是弱化抽象，還有遞歸抽象… 等等程式設計特性
        :::
        > ![](https://i.imgur.com/XU0hiYG.png)

* 若該 View 已經攔截點擊事件，則會觸發 onTouch
    1. `onTouch` 若沒消費該事件，才會傳遞給 `onTouchEvent` 方法
    
    2. `onTouchEvent` 內才會有 onClick 事件
        > ![](https://i.imgur.com/IiFi8Ta.png)

### Activity 接收點擊事件
* Activity (AppCompatActivity)、Windows (PhoneWindow) 之間的關係可以參考另一篇 [**Activity 布局**](https://hackmd.io/-2Uta0yBSbyhNayaev5eyg#AppCompatActivity)，下圖表示了 `AppCompatActivity` 如何與 `PhoneWindow` 產生關係
    > ![](https://i.imgur.com/i89DAQs.png)

* **點擊事件從 ==Activity#dispatchTouchEvent== 開始分析**，一路會分析進入 `PhoneWindow`、`DecorWindow`，最後到達 DecorView#ViewGroup (最頂層 ViewGroup) 中的 **dispatchTouchEvent 方法**
    ```java=
    /**
     *  Activity.java
     */
        public boolean dispatchTouchEvent(MotionEvent ev) {
            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
                onUserInteraction();
            }
            // 這裡的 getWindow 就是 PhoneWindow 類
            // @ 分析 superDispatchTouchEvent
            if (getWindow().superDispatchTouchEvent(ev)) {
                return true;
            }
            return onTouchEvent(ev);
        }

    // -------------------------------------------------------------
    /**
     * PhoneWindow.java
     */    
        private DecorView mDecor;

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


    // -------------------------------------------------------------
    /**
     * DecorView.java
     * 
     * DecorView 繼承於 FrameLayout，而 FrameLayout 並沒有 Overidde 
     * dispatchTouchEvent 方法，所以必須往它的父類尋找
     */ 
        public boolean superDispatchTouchEvent(MotionEvent event) {
            return super.dispatchTouchEvent(event);
        }


    // -------------------------------------------------------------
    /**
     * ViewGroup.java
     * 
     * ViewGroup 繼承於 View
     * ViewGroup 有重寫 dispatchTouchEvent，所以不用繼續往 View 去
     */ 
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            ... 省略
        }
    ```
    :::info
    * 從這裡可以看出 **事件分發會由 PhoneWindow 中的 DecorView 開始**
    :::
    > ![](https://i.imgur.com/vBtHm8o.png)

* **Activity & View 關係圖**

    > ![](https://i.imgur.com/59KfaTv.png)

## 事件處理

透過上面分析，我們就知道點擊 **事件是如何傳遞至 DecorView#ViewGroup 中**，這裡我們會再分 ViewGroup 點擊事件來分析

- **先了解幾個 MotionEvent**

    | MotionEvent 事件 | 動作 | 其他 |
    | -------- | -------- | -------- |
    | ACTION_DOWN | 手指下壓 | 又份為攔截、不攔截 |
    | ACTION_UP | 手指抬起 | 事件結束 |
    | ACTION_MOVE | 在螢幕滑動 | 會被 **多次觸發** |
    | ACTION_CANCEL | 事件取消 | **事件被上層攔截時候觸發** |

### [ViewGroup](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/ViewGroup.java) 處理 `ACTION_DOWN`：ChildView 攔截
* **==Down 事件只會觸發一次== (單點觸控，多點觸控就不只一次)**，攔截就是該 ViewGroup **自己處理事件**，不會對 ChildView 分發

* 每個點擊事件都是以 Action Down 開始，**細節請注意以下的++註解++**，而它主要做的事情有 (這裡會先列出主要的處理項目)
    1. 清除先前的事件：注意 **resetTouchState 方法，它會在 ViewGroup#`dispatchTouchEvent` 事件是 ACTION_DOWN 時執行**

        > ViewGroup 中有一個 member 是 **TouchTarget**：它用來串接該點擊事件
        ```java=
        // ViewGroup.java
            
        // 從該 ViewGroup 開始，往下串接點擊的目標
        private TouchTarget mFirstTouchTarget;

        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            ... 省略部分

            boolean handled = false;
            if (onFilterTouchEventForSecurity(ev)) {

                // 取得目前行為
                final int action = ev.getAction();
                // 與 Mask 進行 and 操作，取得真正的 Action
                final int actionMasked = action & MotionEvent.ACTION_MASK;


                if (actionMasked == MotionEvent.ACTION_DOWN) {
                    // 在 Action Down 時才執行 reset

                    // @ 追蹤 cancelAndClearTouchTargets 方法
                    cancelAndClearTouchTargets(ev);

                    // @ 追蹤 resetTouchState 方法
                    resetTouchState();
                }

            }

            ... 省略部分

            return handled;
        }

        private void resetTouchState() {
            // @ 查看 clearTouchTargets 方法
            clearTouchTargets();
            resetCancelNextUpFlag(this);

            // 清除 FLAG_DISALLOW_INTERCEPT、允許 ViewGroup 攔截
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
            mNestedScrollAxes = SCROLL_AXIS_NONE;
        }

        // 清除第一個點擊 View 串列的所有事件
        private void clearTouchTargets() {
            // TouchTarget 是單向鏈表
            TouchTarget target = mFirstTouchTarget;
            if (target != null) {
                do {
                    TouchTarget next = target.next;
                    // 回收 TouchTarget 方便覆用
                    target.recycle();
                    target = next;
                } while (target != null);

                // @ 將成員 mFirstTouchTarget 至為 null 
                mFirstTouchTarget = null;
            }
        }
        ```

        :::info
        * 我們來試運算，使用上面的 `~&` 公式，是否能清除 `FLAG_DISALLOW_INTERCEPT` 這個 Flag 值
            
            ```shell=
            ## 假設 mGroupFlags 為 0x8A (0b1000 1010)
            ## FLAG_DISALLOW_INTERCEPT 為 0x80 (這是假設值 0b1000 0000) 
            ## 首先反向
            ~FLAG_DISALLOW_INTERCEPT
            ## Ans: 0b0111 1111

            ## 做 And 運算
            mGroupFlags & (0b0111 1111)
            ## (0b1000 1010) & (0b0111 1111)
            Ans: 0b0000 1010
            ```
        :::
        

    2. 是否會呼叫 ViewGroup#**onInterceptTouchEvent** 方法：會有兩個條件，再加上一個 FLAG 判斷：
        1. 目前是 ACTION_DOWN 事件
        
        2. 已經有 ChildView 處理這個點擊事件 (如果有子 View 處理事件就會給 mFirstTouchTarget  賦值)
        
        3. 目前 ViewGroup 是否被禁止攔截 (一般 ViewGroup 接收到 `ACTION_DOWN` 時，如果沒有禁止攔截的話，就會執行 onInterceptTouchEvent 方法)
            :::success
            * **FLAG_DISALLOW_INTERCEPT 判斷**：
                
                可以透過 ViewGroup#requestDisallowInterceptTouchEvent 方法控制，**通常是 ChildView 在要求 Parent 不要攔截使用**

                同時如果 ChildView 如果使用 requestDisallowInterceptTouchEvent 時也要同時禁止 ViewGroup 對 `ACTION_DOWN` 的攔截 (**請看第一點**)
            :::
        ```java=
        // ViewGroup.java
            
        // 從該 ViewGroup 開始，往下串接點擊的目標
        private TouchTarget mFirstTouchTarget;

        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            ... 省略部分

            boolean handled = false;
            if (onFilterTouchEventForSecurity(ev)) {

                // 取得目前行為
                final int action = ev.getAction();
                // 與 Mask 進行 and 操作，取得真正的 Action
                final int actionMasked = action & MotionEvent.ACTION_MASK;

                // Action down 時 ...初始化 FLAG_DISALLOW_INTERCEPT

                final boolean intercepted;
                // 這裡有兩個判斷 決定是否呼叫 ViewGroup 自己的 onInterceptTouchEvent 方法
                // 1. 目前是 ACTION_DOWN 
                // 2. 已經有子 View 處理這個點擊事件 
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || mFirstTouchTarget != null) {

                    // 判斷是否禁止 攔截
                    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                    if (!disallowIntercept) {

                        // 呼叫自身的 onInterceptTouchEvent 方法
                        intercepted = onInterceptTouchEvent(ev);
                        ev.setAction(action); // restore action in case it was changed
                    } else {
                        intercepted = false;
                    }
                } else {
                    intercepted = true;
                }

            }

            ... 省略部分

            return handled;
        }
        ```
        > ![](https://i.imgur.com/UOLS8bz.png)

    3. 若 ViewGroup 自身沒有攔截，就會 ++**遞迴 ChildView**++：並一一執行　**ViewGroup#dispatchTransformedTouchEvent** 分發給每個 ChildView 處理
        - 若 ChildView 消耗事件則返回 true，則跳出迴圈
        
        - 否則往下一個 ChildView 詢問
        

        ```java=
        // ViewGroup
            
        // 從該 ViewGroup 開始，往下串接點擊的目標
        private TouchTarget mFirstTouchTarget;

        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            ... 省略部分

            boolean handled = false;
            if (onFilterTouchEventForSecurity(ev)) {

                // 取得目前行為
                final int action = ev.getAction();
                // 與 Mask 進行 and 操作，取得真正的 Action
                final int actionMasked = action & MotionEvent.ACTION_MASK;

                // Action down 時 ...初始化 FLAG_DISALLOW_INTERCEPT

                final boolean intercepted;
                // 判斷自身 ViewGroup 是否可以攔截事件

                // 目前假設 ViewGroup 不攔截
                if (!canceled && !intercepted) {
                    ...

                    if (actionMasked == MotionEvent.ACTION_DOWN
                            || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                            || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                        ...

                        if (newTouchTarget == null && childrenCount != 0) {
                            ...
                            // 重新排列在該 ViewGroup 中的 ChildView 們的順序
                            final ArrayList<View> preorderedList = 
                                            buildTouchDispatchChildList();
                            ...

                            for (int i = childrenCount - 1; i >= 0; i--) {
                                final int childIndex = getAndVerifyPreorderedIndex(
                                        childrenCount, i, customOrder);
        
                                final View child = getAndVerifyPreorderedView(
                                        preorderedList, children, childIndex);

                                ...

                                // 是否動畫中 canReceivePointerEvents
                                // 點擊是否在 child 元素內 isTransformedTouchPointInView 
                                if (!child.canReceivePointerEvents()
                                        || !isTransformedTouchPointInView(x, y, child, null)) {
                                    ev.setTargetAccessibilityFocus(false);
                                    continue;
                                }

                                // 獲取點擊中的 View 
                                // 新的 View 事件才會返回  非 null
                                newTouchTarget = getTouchTarget(child);
                                if (newTouchTarget != null) {
                                    // Give it the new pointer in addition to the ones it is handling.
                                    // Child View 已經接收到點擊
                                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                                    break;
                                }


                                // @ 重點在 dispatchTransformedTouchEvent 方法
                                if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                    // ChildView 處理事件成功

                                    ... 省略部分

                                    mLastTouchDownX = ev.getX();
                                    mLastTouchDownY = ev.getY();

                                    // @ addTouchTarget 方法
                                    // 內會賦予 mFirstTouchTarget 值
                                    newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                    alreadyDispatchedToNewTouchTarget = true;
                                    // 跳出迴圈
                                    break;
                                }


                            }
                        }

                    }
                }

            }

            ... 省略部分

            return handled;
        }
        ```
        :::success
        * 從這裡可以看出來 **View 是一個多元樹**，並且在這裡使用 [**責任鏈 OOP 設計**](https://devtechascendancy.com/object-oriented_design_chain_framework/)

            > ![](https://i.imgur.com/z60XP2V.png)
        :::

        :::info
        * **按照螢幕上的 Z 軸**，重新排列 ViewGroup 中 ChildView 的順序

            ```java=
            // ViewGroup

            public ArrayList<View> buildTouchDispatchChildList() {
                return buildOrderedChildList();
            }

            ArrayList<View> buildOrderedChildList() {
                // 全部 ChildView 的數量
                final int childrenCount = mChildrenCount;

                ...

                final boolean customOrder = isChildrenDrawingOrderEnabled();
                for (int i = 0; i < childrenCount; i++) {
                    // add next child (in child order) to end of list
                    final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
                    final View nextChild = mChildren[childIndex];
                    final float currentZ = nextChild.getZ();

                    // insert ahead of any Views with greater Z
                    int insertIndex = i;
                    while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) {
                        insertIndex--;
                    }
                    // Z 軸越大則擺放至越前面
                    mPreSortedChildren.add(insertIndex, nextChild);
                }
                return mPreSortedChildren;
            }
            ```
        :::

    * [**ViewGroup**](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/ViewGroup.java)#**dispatchTransformedTouchEvent** 方法：**ViewGroup 會透過該方法把事件傳遞給 ChildView**
        
        > 內部其實就會針對 ChildView 執行  **dispatchTouchEvent 方法**
        
        1. 若是傳入的 child 是 null 就呼叫 ViewGroup 父類的 dispatchTouchEvent
        
        2. 不是 null 則呼叫指定 View 的 dispatchTouchEvent (**當前情況就是有傳入 View 物件，所以會轉跳到指定 View#dispatchTouchEvent 方法**)

            ```java=
            // ViewGroup.java

            private boolean dispatchTransformedTouchEvent(MotionEvent event, 
                                          boolean cancel,
                                          View child, 
                                          int desiredPointerIdBits) {

                final boolean handled;

                ... 省略部分

                // 重點在個判斷
                if (child == null) {
                    // 如果 Child view 為 null 則回傳給 ViewGroup 的 Parent
                    handled = super.dispatchTouchEvent(transformedEvent);
                } else {
                    ... 目前走這裡

                    handled = child.dispatchTouchEvent(transformedEvent);
                }

                return handled;
            }
            ```
    * ViewGroup#**addTouchTarget**：賦予 `mFirstTouchTarget` 成員
        ```java=
        // ViewGroup.java

            private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
                // 取覆用的 TouchTarget 物件

                final TouchTarget target = TouchTarget
                            .obtain(child, pointerIdBits);
                
                // 串接上一個事件的 View
                target.next = mFirstTouchTarget;
                
                // 第一個 處理點擊事件 的 View
                mFirstTouchTarget = target;
                return target;
            }
        ```
        > ![](https://i.imgur.com/XAHPQnv.png)

        :::success
        一開始 target.next 為 null，通過 TouchTarget#obtain 獲得對象 (相當於 new 的功能)，並將 next 只給前一個 View
        
        **最後再將剛剛獲得的對象賦予 `mFirstTouchTarget`** 並返回
        :::

    4. 在 ChildView 接收並攔截事件後 (透過 `dispatchTouchEvent` 方法)，**會賦予該 ViewGroup#mFirstTouchTarget 值**、並跳出迴圈
        
        
* ChildView 處理完事件後，返回到 ViewGroup 繼續處理
    
    ```java=
    // ViewGroup.java

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        ... 省略部分

        if (onFilterTouchEventForSecurity(ev)) {

            ... 省略上面分析 (ChildView 分發，跳出 for 迴圈)

            // 目前情況， ChildView 已經處理事件，所以 mFirstTouchTarget 不為 null
            if (mFirstTouchTarget == null) {
                // 下一小節在分析
            } else {
                ... 目前判斷走這

                TouchTarget predecessor = null;

                // 尋找目前的點擊事件相對的 ChildView

                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;

                while (target != null) {
                    final TouchTarget next = target.next;
                    
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        
                        // 分發事件
                        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;
                }

            }

        }

        ...

        return handled;
    }
    ```
    :::info
    到這裡就分析完 ViewGroup 不攔截事件，ChildView 透過 **View#dispatchTouchEvent** 收到事件
    :::

### [ViewGroup](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/ViewGroup.java) 處理 `ACTION_DOWN`：ChildView 不攔截

:::success
ChildView 不攔截代表 **ViewGroup 要自己決定要不要處理該事件**
:::

* ViewGroup ChildView 攔截事件：處理方式相同 (上一小節)，這裡主要看看不同的部分，該事件由 ViewGroup 自己處理 (dispatchTransformedTouchEvent 方法)

    ```java=
    // ViewGroup.java

        // 從該 ViewGroup 開始，往下串接點擊的目標
        private TouchTarget mFirstTouchTarget;

        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            ... 省略部分

            boolean handled = false;
            if (onFilterTouchEventForSecurity(ev)) {

                // 取得目前行為
                final int action = ev.getAction();
                // 與 Mask 進行 and 操作，取得真正的 Action
                final int actionMasked = action & MotionEvent.ACTION_MASK;


                if (actionMasked == MotionEvent.ACTION_DOWN) {
                    // 在 Action Down 時才執行 reset

                    cancelAndClearTouchTargets(ev);

                    resetTouchState();
                }

            }

            ... 省略部分

            return handled;
        }
    ```

    1. 清除先前的事件：注意 **resetTouchState 方法，它會在 ViewGroup#dispatchTouchEvent 事件是 `ACTION_DOWN` 時執行**
        > 同上，請參考上一小節

    2. 是否會呼叫 ViewGroup#**onInterceptTouchEvent** 方法：會有兩個條件，再加上一個 FLAG 判斷
        1. 目前是 `ACTION_DOWN` 事件
        
        2. 已經有 ChildView 處理這個點擊事件 (如果有子 View 處理事件就會給 `mFirstTouchTarget`  賦值)
        
        3. 目前 ViewGroup 是否被禁止攔截 (一般 ViewGroup 接收到 `ACTION_DOWN` 時，如果沒有禁止攔截的話，就會執行 onInterceptTouchEvent 方法)

            > 同上，請參考上一小節

    3. 若 ViewGroup 自身沒有攔截，就會遞迴 ChildView：並一一執行　**ViewGroup#dispatchTransformedTouchEvent** 分發給每個 ChildView 處理
        ```java=
        // ViewGroup
            
            // 從該 ViewGroup 開始，往下串接點擊的目標
            private TouchTarget mFirstTouchTarget;
        
            @Override
            public boolean dispatchTouchEvent(MotionEvent ev) {
                ... 省略部分

                boolean handled = false;
                if (onFilterTouchEventForSecurity(ev)) {
                    ...
                        
                    if (!canceled && !intercepted) {
                        // 這邊會循環該 ViewGroup 中的所有的 ChildView，看有沒有 View 處理事件
                        // 
                        // 目前狀況是都沒有 ChildView 要處理這個事件
                    }
                }
                ...
            }
        ```

    4. ChildView 接收但 **全部都不攔截事件**，**ViewGroup#mFirstTouchTarget 為 null**
        ```java=
        // ViewGroup.java

            @Override
            public boolean dispatchTouchEvent(MotionEvent ev) {

                ... 省略部分

                if (onFilterTouchEventForSecurity(ev)) {

                    ... 省略上面分析 (ChildView 分發)

                    // 目前情況，沒有 ChildView 處理事件
                    if (mFirstTouchTarget == null) {
                        // 注意傳入的第三個參數 null
                        handled = dispatchTransformedTouchEvent(ev, canceled, null,
                                TouchTarget.ALL_POINTER_IDS);
                    } else {
                        ...
                    }

                }

                ...

                return handled;
            }


            private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
                    View child, int desiredPointerIdBits) {
                final boolean handled;

                ... 省略部分

                if (child == null) {
                    // 回到 ViewGroup 的 Parent dispatchTouchEvent
                    handled = super.dispatchTouchEvent(transformedEvent);

                } else {
                    ...
                }
                ...
                return handled;
            }
        ```
        
        :::info
        這裡可以看出 **ViewGroup 預設不處理事件**，直接將事件返回到上一層 View
        :::

### [View](https://cs.android.com/android/platform/superproject/+/android-10.0.0_r1:frameworks/base/core/java/android/view/View.java) 收到事件：處理 `ACTION_UP`

:::success
假設 ViewGroup 不攔截事件，最終會呼叫目標 View#`dispatchTouchEvent` 方法，**讓 View 處理事件**
:::

* **View 首先分發給 onTouch 若是沒有處理則分發到 onTouchEvent**，順序如下
    1. 判斷 `onTouch` 接口：**最先執行　onTouch　接口，若是已經處理，就　onTouchEvent　就不會接收到事件**
    2. 判斷 `onTouchEvent` 接口：若 onTouch 沒有處理這個事件，就會輪到該 View 的 onTouchEvent 處理事件

        ```java=
        // View.java

            public boolean dispatchTouchEvent(MotionEvent event) {
                ... 省略部分

                boolean result = false;

                ... 省略部分

                if (onFilterTouchEventForSecurity(event)) {
                    ...

                    // 最一開始事件分發到 onTouch
                    ListenerInfo li = mListenerInfo;
                    if (li != null && li.mOnTouchListener != null
                            // 判斷該 View 是 enable
                            && (mViewFlags & ENABLED_MASK) == ENABLED
                            && li.mOnTouchListener.onTouch(this, event)) {
                        result = true;
                    }
                    
                    // @ 分析 onTouchEvent
                    if (!result && onTouchEvent(event)) {
                        result = true;
                    }
                }

                ... 省略

                return result;
            }
        ```

* 在 View#**onTouchEvent** 方法中，可以看到 onClick、onLongClick 接口的呼叫

    ```java=
    // View.java

        // PerformClick 代表該 View 的點擊事件
        private PerformClick mPerformClick;

        public boolean onTouchEvent(MotionEvent event) {

            final float x = event.getX();
            final float y = event.getY();
            final int viewFlags = mViewFlags;
            final int action = event.getAction();


            // 只要設定 CLICKABLE、LONG_CLICKABLE、CONTEXT_CLICKABLE 其中一個，
            // 那該 View 就是可點擊的
            final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                    || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;


            // 點擊事件的代理 TouchDelegate
            if (mTouchDelegate != null) {
                if (mTouchDelegate.onTouchEvent(event)) {
                    return true;
                }
            }


            if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
                switch (action) {

                    case MotionEvent.ACTION_UP:
                        ... 省略部分

                        boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;

                        if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                            ...

                            // 長按任務 mHasPerformedLongPress
                            if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                                // 移除長按 Callback
                                removeLongPressCallback();
                                // Only perform take click actions if we were in the pressed state
                                if (!focusTaken) {

                                    if (mPerformClick == null) {
                                        mPerformClick = new PerformClick();
                                    }

                                    // @ 分析 performClickInternal
                                    if (!post(mPerformClick)) {
                                        performClickInternal();
                                    }
                                }
                            }

                            ...
                        }
                        mIgnoreNextUpEvent = false;
                        break;
                }
            }
        }
    ```
    
* 這邊可以看到 View 的點擊事件是 **透過 Handler 傳送 Click 點擊任務 (Runnable)，這樣就不會造成點擊事件堵塞**
    ```java=
    // View.java

        public boolean post(Runnable action) {
            final AttachInfo attachInfo = mAttachInfo;
            
            if (attachInfo != null) {
                // 將 Click 任務放入 Handler 
                return attachInfo.mHandler.post(action);
            }
            getRunQueue().post(action);
            return true;
        }

        private boolean performClickInternal() {
            notifyAutofillManagerOnClick();
            
            // @ 分析 performClick
            return performClick();
        }

        public boolean performClick() {
            notifyAutofillManagerOnClick();
            
            final boolean result;
            final ListenerInfo li = mListenerInfo;
            
            if (li != null && li.mOnClickListener != null) {
                playSoundEffect(SoundEffectConstants.CLICK);
                // 執行使用者的 OnClick 接口
                li.mOnClickListener.onClick(this);
                result = true;
            } else {
                result = false;
            }
            
            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
            notifyEnterOrExitForAutoFillIfNeeded(true);
            return result;
        }
    ```
    > ![](https://i.imgur.com/sswPfLY.png)



### ViewGroup：ACTION_DOWN 總結
1. 判斷事件是否被 ViewGroup 攔截：
    * ViewGroup 攔截事件 -> 直接走 `3`
    
    * ViewGroup 不攔截事件 -> 先走 `2` 再走 `3`

2. ViewGroup 不攔截，並有其中一個 ChildView 攔截事件並處理
    * ViewGroup#newTouchTarget 被賦予值
    
    * 分發到自身的 View (`super.dispatchTouchEvent` 呼叫自己的父類)
    
        ```java=
        // ViewGroup.java

        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
            ... 省略部分

            mLastTouchDownX = ev.getX();
            mLastTouchDownY = ev.getY();

            // @ addTouchTarget 方法內會賦予 mFirstTouchTarget 值
            newTouchTarget = addTouchTarget(child, idBitsToAssign);
            alreadyDispatchedToNewTouchTarget = true;
            // 跳出迴圈
            break;
        }
        ```

3. 事件分發、處理：有兩種情況
    * 所有 ChildView 不攔截這個事件，**所以 mFirstTouchTarget 為 null**，相當於是最後一個 View
        > 因為沒有任何 ChildView 處理，所以 **ViewGroup#View 自己處理 Down 事件**
        
        ```java=
        // 分發程式

        if (mFirstTouchTarget == null) {
            // 第三個參數為 null
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                TouchTarget.ALL_POINTER_IDS);
        }
        ```
    * 有 ChildView 攔截，**所以 mFirstTouchTarget 不為 null**，並且 while 只循環一次，因為 **ChildView 已處理 (分發時處理)**
        
        ```java=
        // 分發程式

        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
            handled = true;
        }
        ```


              

### ACTION_MOVE：View 收到 MOVE
* **Move 事件仍會先走 DecorView 這個 ViewGroup**，在執行 `ACTION_MOVE` 事件時先需要知道幾件事情
    1. **ViewGroup 不會清理點擊的 Flag**
    
    2. TouchTarget 類型的 **`mFirstTouchTarget` 元素 ++不為空++** (因為已經有 ChildView 元素處理)

* ACTION_MOVE 事件的判斷流程圖如下，它主要做的事情是
    1. ViewGroup 是否攔截事件 (以下預設不攔截事件)
    
    2. 不攔截、**==Move 不分發事件==**
    
    3. 分發 or 處理
        
        > ![](https://i.imgur.com/NzZTMfj.png)

* 同樣先來觀察 ViewGroup#dispatchTouchEvent 方法，並從這裡開始分析
    
    1. 不會進入 reset 環節
        ```java=
        // ViewGroup.java

            @Override
            public boolean dispatchTouchEvent(MotionEvent ev) {

                // 不清理 Flag，不會進入 reset 環節
                if (actionMasked == MotionEvent.ACTION_DOWN) {
                    ... 省略
                }
            }

        ```
    2. 判斷當前 ViewGroup 是否有禁止攔截 `FLAG_DISALLOW_INTERCEPT`，如果沒有的話就調用自身的 `onInterceptTouchEvent` 方法 檢查是否攔截

        ```java=
        // ViewGroup.java

            @Override
            public boolean dispatchTouchEvent(MotionEvent ev) {
                
                ... 省略部分

                // Check for interception.
                // 由於 mFirstTouchTarget 不為空，所以會進入
                final boolean intercepted;
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || mFirstTouchTarget != null) {

                    // 判斷 ViewGroup 是否禁止攔截
                    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

                    // 預設 ViewGroup 不攔截
                    if (!disallowIntercept) {
                        intercepted = onInterceptTouchEvent(ev);
                        ev.setAction(action); // restore action in case it was changed
                    } else {
                        intercepted = false;
                    }
                } else {
                    intercepted = true;
                }
            }
        ```

    3. 不是 `ACTION_DOWN` 事件，所以不會進入最初的 `ACTION_DOWN` 事件分發環節
        ```java=
        // ViewGroup.java

            @Override
            public boolean dispatchTouchEvent(MotionEvent ev) {

                    ... 省略部分

                // 注意這兩個區域變數，下面會用到
                TouchTarget newTouchTarget = null;
                boolean alreadyDispatchedToNewTouchTarget = false;
                // 進入
                if (!canceled && !intercepted) {

                    // 2. 不是 Down 事件，所以不進入
                    if (actionMasked == MotionEvent.ACTION_DOWN
                            || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                            || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                        // 省略... 由於不進入，所以不會對 ChildView 分發

                    }
                }

                ... 省略部分
            }
        ```
    4. `ACTION_MOVE` 的重點在事件分發 (請看註解)
        ```java=
        // ViewGroup.java

            @Override
            public boolean dispatchTouchEvent(MotionEvent ev) {

                ... 省略部分

                // mFirstTouchTarget 不為空 (因為已經是 View 處理事件)
                if (mFirstTouchTarget == null) {
                    ...

                } else {
                    // ++重點在這++TouchTarget predecessor = null;

                    TouchTarget target = mFirstTouchTarget;
                    while (target != null) {
                        final TouchTarget next = target.next;

                        // alreadyDispatchedToNewTouchTarget 是 false
                        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                            handled = true;

                        } else {
                            final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                    || intercepted;

                            // @ 分析 dispatchTransformedTouchEvent 分發給 ChildView
                            if (dispatchTransformedTouchEvent(ev, cancelChild,
                                    target.child, target.pointerIdBits)) {
                                handled = true;
                            }
                            ...
                        }
                        predecessor = target;
                        target = next;
                    }
                }
            }
        ```

* 接下來透過 dispatchTransformedTouchEvent 方法：分發 `ACTION_MOVE` 事件給 ChildView 的 dispatchTouchEvent
    ```java=
    // ViewGroup.java 

        private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
                View child, int desiredPointerIdBits) 

            ...省略部分

            if (child == null) {
                ...
            } else {
                // 加設有 ChildView 則會進入這裡
                final float offsetX = mScrollX - child.mLeft;
                final float offsetY = mScrollY - child.mTop;
                transformedEvent.offsetLocation(offsetX, offsetY);
                if (! child.hasIdentityMatrix()) {
                    transformedEvent.transform(child.getInverseMatrix());
                }

                handled = child.dispatchTouchEvent(transformedEvent);
            }
        }
    ```
    
### ACTION_MOVE：ViewGroup 攔截 MOVE

* 先了解目前情況：已經有 ChildView 處理 ACTION_DOWN 事件，而我們自訂一個 ViewGroup 並複寫 ViewGroup#**onInterceptTouchEvent** 方法，**==在 `ACTION_VIEW` 時返回 true 攔截==**
    ```java=
    // 自定義 ViewGroup.java

    public class MyViewGroup extends View {

        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            if(ev.getAction() == MotionEvent.ACTION_MOVE) {
                // ViewGroup 自己消耗事件
                return true;
            }
            return super.onInterceptTouchEvent(ev);
        }

    }
    ```

* **ACTION_MOVE 事件是多次發生**
    1. 第一個 ACTION_MOVE （++從 ParentView 進來++）的目的是：^1.^ 取消 ChildView 事件（**事件改為 ACTION_CANCEL**）、^2.^ 將 **ViewGroup#mFirstTouchTarget 置為空**，這時 ParentView 是不處理事件的

        結果：這時 **ChildView 會收到 ACTION_CANCEL 事件** (事件被取消，之後都會由 ViewGroup 處理事件)
        ```java=
        /**
         * ViewGroup.java
         * 
         * 由於複寫 onInterceptTouchEvent 方法會讓 Move 事件為 ture，所以 cancelChild 也為 ture，
         * 傳入 dispatchTransformedTouchEvent 方法的 cancel 參數為 ture，
         * 
         * 這時事件就會被改為 ACTION_CANCEL，下面的 ChildView 就會接收到 ACTION_CANCEL 事件
         */ 
            @Override
            public boolean dispatchTouchEvent(MotionEvent ev) {

                //... 省略

                if (mFirstTouchTarget == null) {
                    ...

                } else {

                    TouchTarget predecessor = null;
                    TouchTarget target = mFirstTouchTarget;
                    while (target != null) {
                        final TouchTarget next = target.next;
                        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                            handled = true;
                        } else {
                            // ACTION_MOVE 第一次進入
                            // ^1^ 重點 intercepted 是 true，所以 cancelChild = true
                            final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                    || intercepted;

                            if (dispatchTransformedTouchEvent(ev, cancelChild,
                                    target.child, target.pointerIdBits)) {
                                handled = true;
                            }

                            // ^2^ 重點，mFirstTouchTarget = next，next 為 null
                            // 所以 mFirstTouchTarget = null
                            if (cancelChild) {
                                if (predecessor == null) {
                                    // next 為空，所以 mFirstTouchTarget 置為空 (next 為空)
                                    mFirstTouchTarget = next;
                                } else {
                                    predecessor.next = next;
                                }
                                target.recycle();
                                target = next;
                                continue;
                            }
                        }
                        predecessor = target;
                        target = next;
                    }
                }

                ...
            }


            private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
                    View child, int desiredPointerIdBits) {

                final boolean handled;

                final int oldAction = event.getAction();
                // cancel 為 true 會進入
                if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {

                    // 改變事件為 ACTION_CANCEL
                    event.setAction(MotionEvent.ACTION_CANCEL);
                    if (child == null) {
                        ...
                    } else {
                        // 往下分發 ChildView 的事件就為 cancel
                        // 事件被上層攔截時觸發
                        handled = child.dispatchTouchEvent(event);
                    }
                    event.setAction(oldAction);
                    return handled;
                }

                ... 省略部分
                    
                return handled;
            }

        ```
    
    2. 第二個 ACTION_MOVE（從 ParentView 進來），由於 mFirstTouchTarget 為空，所以 ParentView 不會分發
        
        結果：**ParentView 自己處理事件**
        ```java=
        /**
         * ViewGroup.java
         */ 
            @Override
            public boolean dispatchTouchEvent(MotionEvent ev) {

                    // 在第一個 ACTION_MOVE 時 mFirstTouchTarget 被置為空，所以不會進入
                    if (actionMasked == MotionEvent.ACTION_DOWN
                            || mFirstTouchTarget != null) {

                        // ...省略，因為 mFirstTouchTarget = null
                    } else {
                        intercepted = true;
                    }

                    // 由於 intercepted = true 也就是攔截，就不會進入
                    if (!canceled && !intercepted) {
                        //... 分發部份
                    }


                    // 進入
                    if (mFirstTouchTarget == null) {
                        // ChildView 為空 (第三個參數)
                        handled = dispatchTransformedTouchEvent(ev, canceled, null,
                                TouchTarget.ALL_POINTER_IDS);
                    } else {

                        ...
                    }

                    ...
            }


            private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
                    View child, int desiredPointerIdBits) {

                final boolean handled;

                ... 省略部分
                
                
                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());
                    }

                    handled = child.dispatchTouchEvent(transformedEvent);
                }

                // Done.
                transformedEvent.recycle();
                return handled;
            }
        ```

## 事件衝突

為何會發生衝突 ? 因為**事件只會有一個**，若是響應的元件不是自己要的 View 原件，這時就可以稱之為事件衝突

> Ex: ViewPager 包裹 RecyclerView

* 分為兩種方法處理
    
    1. **內部攔截法**：需要 ChildView#dispatchTouchEvent 處理 & 需要配合改動父容器 (ParentView) 的 onInterceptTouchEvent 攔截
    
    2. **外部攔截法** (較常使用)：只需要 ParentView 處理

### 內部攔截：ChildView 處理
* **在 ChildView 中使用 `requestDisallowInterceptTouchEvent` 方法，控制 `FLAG_DISALLOW_INTERCEPT` 元素，讓 ParentView 不會往下分發事件** (先介紹這裡的坑~)

* 內部攔截作法 - 2 個重點步驟 (都必須)
    > 以下假設，ChildView 只處理垂直滑動、水平滑動給 ParentView 處理
    1. ChildView 在適當時機透過 `requestDisallowInterceptTouchEvent` 方法，要求 ParentView 不要攔截事件
        ```java=
        // 自訂 View

        public class MyView extends View {

            int dispatchX, dispatchY;

            @Override
            public boolean dispatchTouchEvent(MotionEvent event) {
                int x = (int) event.getX();
                int y = (int) event.getY();

                switch(event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        // 要求 ParentView 不攔截事件
                        getParent().requestDisallowInterceptTouchEvent(true);
                        break;

                    case MotionEvent.ACTION_UP:
                        break;

                    case MotionEvent.ACTION_MOVE:
                        int deltaX = x - dispatchX;
                        int deltaY = y - dispatchY;
                        
                        // 水平時，事件交由 ViewGroup 處理
                        if(Math.abs(deltaX) > Math.abs(deltaY)) {
                            getParent().requestDisallowInterceptTouchEvent(false);
                        }
                        break;
                }

                dispatchX = x;
                dispatchY = y;

                return super.dispatchTouchEvent(event);
            }

        }
        ```
    2. 重寫 ParentView 的 `onInterceptTouchEvent` 方法，設定在 ACTION_DOWN 時不攔截事件，這樣事件才能傳入 ChildView
        ```java=
        // 自訂 ViewGroup

        public class MyViewGroup extends ViewGroup {

            @Override
            public boolean onInterceptTouchEvent(MotionEvent ev) {
                if(ev.getAction() == MotionEvent.ACTION_DOWN) {
                    // ACTION_DOWN 時強制 ViewGroup 不攔截事件
                    return false;
                }
                return super.onInterceptTouchEvent(ev);
            }

        }
        ```


* `requestDisallowInterceptTouchEvent` 細節說明: 為啥  requestDisallowInterceptTouchEvent 無法控制 ACTION_DOWN 事件
    :::warning
    * 發生原因
    
        ViewGroup#`ACTION_DOWN` 事件，會清除 ViewGroup 的禁止攔截事件，所以就算 ChildView 有要求 ViewGroup 不攔截事件，但事件都無法傳入 ChildView 就沒有用了
    :::
    ```java=
    // ViewGroup.java

        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {

            if (onFilterTouchEventForSecurity(ev)) {
                final int action = ev.getAction();
                final int actionMasked = action & MotionEvent.ACTION_MASK;

                // Handle an initial down.
                if (actionMasked == MotionEvent.ACTION_DOWN) {

                    cancelAndClearTouchTargets(ev);
                    // @ 查看 resetTouchState
                    resetTouchState();
                }

                // Check for interception.
                final boolean intercepted;
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || mFirstTouchTarget != null) {    // mFirstTouchTarget != null 滿足

                    // 控制 FLAG_DISALLOW_INTERCEPT
                    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                    // 如果為 ture 則 ParentView 就不會往下分發
                    if (!disallowIntercept) {
                        intercepted = onInterceptTouchEvent(ev);
                        ev.setAction(action); // restore action in case it was changed
                    } else {
                        intercepted = false;
                    }
                } else {
                    intercepted = true;
                }

                // 省略其他...
            }
        }


        // 這個方法可以控制 Flag
        @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);
            }
        }
    ```

    1. 從上面可看出 ViewGroup 時 ACTION_DOWN 使用 **resetTouchState() 方法**，這裡會清理 `FLAG_DISALLOW_INTERCEPT` 這個 Flag，導致事件 ACTION_DOWN 一定會被分發

        ```java=
        // ViewGroup.java
            @Override
            public boolean dispatchTouchEvent(MotionEvent ev) {
                
                ... 省略部分

                final boolean intercepted;

                if (actionMasked == MotionEvent.ACTION_DOWN
                        || mFirstTouchTarget != null) {

                    // 清理後 FLAG_DISALLOW_INTERCEPT 為空，結果就是為 false
                    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                    
                    if (!disallowIntercept) {
                        // 判斷 ViewGroup#onInterceptTouchEvent 是否擷取
                        intercepted = onInterceptTouchEvent(ev);
                        ev.setAction(action); // restore action in case it was changed
                    } else {
                        // ++不擷取++ 注意 intercepted 變數
                        intercepted = false;
                    }
                }
                ...
            }
        ```
    2. 解坑：**ViewGroup 中複寫 `onInterceptTouchEvent`，讓 ViewGroup 在 ACTION_DOWN 事件時返回 false（不擷取），這樣 ACTION_DOWN 就會分發，==這樣才能往下分發到 ChildView==**，而其他的動作則會攔截
        
        :::success
        原因是因為 ViewGroup#`resetTouchState` 會清除 `FLAG_DISALLOW_INTERCEPT`，導致上面的設定失效
        :::
        
        ```java=
        /**
         * 自定義 ViewGroup 內重新 Override
         */ 
            @Override
            public boolean onInterceptTouchEvent(MotionEvent ev) {
                // ACTION_DOWN 事件時，就不擷取，分發到 ChildView
                if(ev.getAction() == ACTION_DOWN) {
                    return false;
                }
                return true;
            }
        ```

### 外部攔截：ParentView 處理
* 由 ParentView 處理事件攔截比較簡單，只需要覆寫 ParentView#**onInterceptTouchEvent** 決定哪個時間（條件）攔截事件
    
    > 以下假設 水平滑動，就攔截事件給 ParentView#onTouchEvent 處理 !
    ```java=
    // 自定義的 ViewGroup.java

    public class MyExternalViewGroup extends ViewGroup {

        int interceptX, interceptY;

        @Override
        public boolean onInterceptTouchEvent(MotionEvent event) {
            boolean intercept = false;
            boolean defaultIntercept = super.onInterceptTouchEvent(event);

            int x = (int) event.getX();
            int y = (int) event.getY();

            switch(event.getAction()) {
                case MotionEvent.ACTION_MOVE:
                    int deltaX = x - interceptX;
                    int deltaY = y - interceptY;
                    
                    // 水平滑動，就攔截 !
                    if(Math.abs(deltaX) > Math.abs(deltaY)) {
                        intercept = true;
                    } else {
                        intercept = false;
                    }
                    break;

                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_DOWN:
                    // 不攔截事件
                    intercept = false;
                    break;
                    
                default:
                    intercept = defaultIntercept;
                    break;
            }

            interceptX = x;
            interceptY = y;

            return intercept;
        }
    }
    ```

## Appendix & FAQ

:::info
:::

###### tags: `Android Framework`
