Chain 責任鏈模式 | 解說實現 | Android Framework View 事件傳遞

Chain 責任鏈模式 | 解說實現 | Android Framework View 事件傳遞

Overview of Content

如有引用參考請詳註出處,感謝

在這份文章中,我們將深入探討 Chain責任鏈模式),一種在軟體開發中常被使用的設計模式。本文將從使用場景著手,探討責任鏈在不同情境下的應用。我們將分類責任鏈的不同形式,並透過相應的 UML 圖表進行清晰的定義。此外,我們將深入探討責任鏈模式的優缺點,以幫助讀者更好地理解其適用性和局限性。

在「Chain 實作」一節中,我們將探討責任鏈的實際實作。我們將首先介紹不分離請求的標準模式,其中各個元件共同處理請求。接著,我們將討論分離請求的標準模式,其中每個元件專注處理特定類型的請求,形成一個有序的處理鏈。

最後,我們將深入研究 Android Framework 中的相關實例。透過探討 ViewGroup 事件分發和事件傳遞的方法,我們將更具體地理解 Android 框架中如何應用責任鏈模式來有效處理事件流

這份文章將為讀者提供深入的 Chain(責任鏈模式)相關知識,不僅理論豐富,還包含實際的實作例子,以幫助讀者更好地應用這一強大的設計模式。


Chain 使用場景

這是資料結構 Array 單向鏈結 的一種應用,串連多個物件,使其都有機會處理請求

● 多個物件 1. 有順序的處理、2. 同一請求

● 具體要由哪個物件處理可由 運行時動態決定

Chain 責任鏈分類

● 責任鏈有兩種類型,如下表

責任鏈類型說明
純 - 責任鏈需求可被責任鏈消費
不純 - 責任鏈責任鏈不能處理需求

Chain 定義 & Chain UML

Chain 定義

多個物件都有機會處理請求,避免請求者(request)、接收處理者(handle)耦合;將對象鏈成一條鏈,並沿著鏈傳遞直到有處理者

● 處理的邏輯重點在 鏈上

Chain UML 有分為兩種:分別都有兩個行為 請求、處理

A. 合併 請求、處理:這裡我們主要是關注 Linked 的處理

類別功能
LinkedItem (鏈、抽象)其內部有 0 ~ 1 個 LinkedItem,判斷是否讓下一個 LinkedItem 處理;這裡 有 2 個選擇,不是 處理,就是 往下傳遞
ConcreateA、B (實作)處理使用者的請求 (請求、處理一起)

LinkedItem 有三個責任

A. 對外開放一個請求處理入口( handleRequest

B. 設定鏈的方式 (setNext)

C. 具體的請求者的抽象方法

● 抽象定義能處理的級別

● 抽象定義每個子類處理的方式

B. 分離 請求、處理:我們在基礎的責任鏈上將 請求處理 分開

● 這裡的重點是使用 依賴倒置 概念,將 請求處理 抽象化

類別功能
LinkedItem (鏈、抽象)其內部有 0~1 個 LinkedItem,判斷是否讓下一個 LinkedItem 處理;這裡 有 2 個選擇,不是 1.處理,就是 2.往下傳遞
ConcreateLinkedA、B(實作)處理使用者的請求
Request (抽象)抽象化共同處理的請求
ConcreateRequestA、B(實作)定義請求的詳細規則

Chain 優缺點

Chain 設計優點

● 自由的決定要由何處開始處理這個任務

● 符合單一職責:一個類只負責它自身要處理的有範圍的邏輯

● 符合迪米特原則:不用知道別的類如何處理,只須關注自己的部份,其他交給父類處理

請求者(Request)與 處理者(Handler)解偶

● 處理者不用知道請求的全貌,只須關注部份(關注自己能不能處理、處理方式)

● 請求者也無須知道是哪個類處理的,只須關注結果即可

Chain 設計缺點

● 搜尋速度較慢(如 Linked List 的特性)

可以 setNext 時設置一個閥值,避免無意識的破壞調系統性能(不好查)

● 採用遞歸方式運作,也就導致了複雜度增加、可讀性降低

Chain 實作

Chain 標準實現:不分離請求

● 現在假設在申報出國出差的經費,經費需要經過不同主管批省

A. 批省要有順序

B. 每個主管的額度也不相同

A. LinkedItem (抽象)1. 定義相同行為,並且內部有 2. 0 ~ 1 個相同的抽象成員 (指向 next 處理對象)

● 這裡要特別注意,結束條件:如果沒有設定結束條件,會導致 Function 堆疊超出 (StackOverflow 錯誤)


// LinkedItem (抽象)

public abstract class LinkedItem {

    private final String handlePersonName;

    public LinkedItem() {
        // 簡單定義處理者
        this.handlePersonName = getClass().getSimpleName();
    }

    public LinkedItem next;

    public final boolean start(int money) {
        boolean canHandle = handle(money);

        if(!canHandle) {
            System.out.println(handlePersonName + " cannot pay.");

            // 將處理傳遞給下一個對象
            // 結束條件 next == null
            return next != null && next.start(money);

        } else {
            System.out.println(handlePersonName + " handle pay: " + money);
        }


        return true;
    }

    // 1. 為相同行為定義抽象
    protected abstract boolean handle(int money);

}

● 從這裡可以看出 模板設計的影子 (固定調用抽象 handle 方法)

B. ConstractA、B:定義實體處理的子類,並在這裡 由各個子類自行判斷是否處理,在這裡返回的 結果將會決定 Linked 是否繼續往下執行


// ConstractA、B

public class Supervisor extends LinkedItem {

    protected static final int MONEY_LIMIT = 1_000;

    @Override
    protected boolean handle(int money) {
        return money <= MONEY_LIMIT;
    }
}

// -----------------------------------------------------
public class Manager extends LinkedItem {

    protected static final int MONEY_LIMIT = 10_000;

    @Override
    protected boolean handle(int money) {
        return money <= MONEY_LIMIT;
    }
}

// -----------------------------------------------------
public class CEO extends LinkedItem {

    protected static final int MONEY_LIMIT = 100_000;

    @Override
    protected boolean handle(int money) {
        return money <= MONEY_LIMIT;
    }
}

User 使用:使用者可以自行串接處理的順序,並決定啟動時機


// User

public class Main {

    public static void main(String[] args) {
        // 創建實際處理的子類
        LinkedItem ceo = new CEO();
        LinkedItem manager = new Manager();
        LinkedItem supervisor = new Supervisor();

        // User 自行串接處理順序
        supervisor.next = manager;
        manager.next = ceo;

        System.out.println("Linked result: " + supervisor.start(1_000));
        System.out.println("--------------------------------\n");
        System.out.println("Linked result: " + supervisor.start(10_000));
        System.out.println("--------------------------------\n");
        System.out.println("Linked result: " + supervisor.start(100_000));
        System.out.println("--------------------------------\n");
        System.out.println("Linked result: " + supervisor.start(1_000_000));

    }

}

● final 關鍵字:

使用 Java 類加載的特性,定義 final 變量 最慢 必須在建構函數中定義

--實作--

Chain 標準實現拓展:分離請求

● 現在除了上面的需求外還有新增一個需求:需要填入申請者的資料 (申請者名稱、Project 名稱、花費)

A. Request:定義新增的 Request 抽象類


public interface Request {
    String getRequestName();

    String projectName();

    int cost();
}

B. ConcreateRequestA、B:定義具體的 Request 處理細節


public class Alien implements Request {
    @Override
    public String getRequestName() {
        return "Alien";
    }

    @Override
    public String projectName() {
        return "Sky";
    }

    @Override
    public int cost() {
        return 1_000;
    }
}

// -----------------------------------------------
public class Pan implements Request {
    @Override
    public String getRequestName() {
        return "Pan";
    }

    @Override
    public String projectName() {
        return "Pandora";
    }

    @Override
    public int cost() {
        return 10_000;
    }
}

// -----------------------------------------------
public class Shanks implements Request {
    @Override
    public String getRequestName() {
        return "Shanks";
    }

    @Override
    public String projectName() {
        return "Comics";
    }

    @Override
    public int cost() {
        return 100_000;
    }
}

C. LinkedItem 抽象:也就是處理類的抽象,主要修改接收參數,LinkedItem 抽象 參數依賴抽象 Request


public abstract class LinkedItem {

    private final String handlePersonName;

    public LinkedItem() {
        this.handlePersonName = getClass().getSimpleName();
    }

    public LinkedItem next;

    // 主要修改接收參數,讓參數依賴抽象 Request
    public final boolean start(Request request) {
        // 獲取具體花費
        int cost = request.cost();
        boolean canHandle = handle(cost);

        if(!canHandle) {
            System.out.println(handlePersonName + " cannot pay.");

            return next != null && next.start(request);

        } else {
            // 獲取 Request name、projectName
            System.out.println("Person: " + request.getRequestName() + ", project" + request.projectName());
            System.out.println(handlePersonName + " handle pay: " + cost);
        }


        return true;
    }

    protected abstract boolean handle(int money);

}

D. ConcreateLinkedA、B:具體實現抽象 Linked 細節,同上個範例,沒有做修改

請參考上一小節的程式範例

● User 使用:這裡較不一樣的是使用者必須自訂 Request 需求


public class Main {

    public static void main(String[] args) {
        LinkedItem ceo = new CEO();
        LinkedItem manager = new Manager();
        LinkedItem supervisor = new Supervisor();

        supervisor.next = manager;
        manager.next = ceo;

        System.out.println("Linked result: " + supervisor.start(new Alien()));
        System.out.println("--------------------------------\n");
        System.out.println("Linked result: " + supervisor.start(new Pan()));
        System.out.println("--------------------------------\n");
        System.out.println("Linked result: " + supervisor.start(new Shanks()));

    }

}

● 這裡可以注意到一點,使用者(高層模塊)通常會使用類一個封裝過得類來進行請求,而不是自己串接任務鏈

可以使用創建類型:像是 FactoryBuilder 設計都可以

--實作--

Android Source View 事件

Android View 的傳遞事件就是使用 Linked 模式,可以參考另一篇 View 事件分發

透過 dispatchTouchEvent 方法來迭代

ViewGroup 事件分發 dispatchTouchEvent

● 這裡我們直接從 ViewGroup 接收的 dispatchTouchEvent 方法開始分析

簡單來說,都是傳遞的事件會從 PhoneWindow 的 DecorView (ViewGroup) 往下傳遞事件,傳遞方式是使用 遞迴呼叫 (DFS)

A. 清理當前 View 的事件:如果是 ACTION_DOWN 事件,代表它是一個新的事件,需要清理當前 ViewGroup 的一些設定

關注變數當前數值
actionMaskedMotionEvent.ACTION_DOWN
// ViewGroup.java

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

    ...

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

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

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

            // 在新事件開始時,先處理上一個事件 (透過 Cancel、Clear)
            cancelAndClearTouchTargets(ev);

            resetTouchState();
        }

        ...
    }

    ...
}

cancelAndClearTouchTargets 方法:透過 TouchTarget Linked 將 cancel 事件傳遞給所有 view


// ViewGroup.java

private void cancelAndClearTouchTargets(MotionEvent event) {
    if (mFirstTouchTarget != null) {
        boolean syntheticEvent = false;
        if (event == null) {
            final long now = SystemClock.uptimeMillis();
            event = MotionEvent.obtain(now, now,
                    MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
            event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
            syntheticEvent = true;
        }

        for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
            resetCancelNextUpFlag(target.child);

            // 第二個參數 true, 代表 cacel 事件
            // 傳遞所有 view cancel 事件
            dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
        }
        clearTouchTargets();

        if (syntheticEvent) {
            event.recycle();
        }
    }
}

resetTouchState 方法:清除 FLAG_DISALLOW_INTERCEPT FLAG:不同意 ViewGroup 中斷


// ViewGroup.java

private TouchTarget mFirstTouchTarget;

private void resetTouchState() {
    clearTouchTargets();
    resetCancelNextUpFlag(this);
    mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    mNestedScrollAxes = SCROLL_AXIS_NONE;
}

// 透過該方法將 mFirstTouchTarget 設置為 null
private void clearTouchTargets() {
    TouchTarget target = mFirstTouchTarget;
    if (target != null) {
        do {
            TouchTarget next = target.next;
            target.recycle();
            target = next;
        } while (target != null);
        mFirstTouchTarget = null;
    }
}

B. 檢查該 ViewGroup 中斷事件

關注變數當前數值
actionMaskedMotionEvent.ACTION_DOWN
mFirstTouchTargetnull

// ViewGroup.java

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

    ...

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

        ... 清除上一個點擊事件

        // 檢查中斷
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN    // 成立
                || mFirstTouchTarget != null) {        // 不成立

            // 判斷 viewgroup flag 是否不允許中斷
            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 {
            // 並非下壓事件 & 有 childView 已經在處理事件
            intercepted = true;
        }

        ...
    }
}     

C. 檢查事件是否被取消


// ViewGroup.java

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

    ...

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

        // 檢查事件是否被取消
        final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;

        // Update list of touch targets for pointer down, if needed.
        // 如果有需要的話需要對所有 down 事件檢查
        final boolean isMouseEvent = ev.getSource() == InputDevice.SOURCE_MOUSE;
        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0
                && !isMouseEvent;

D. ViewGroup 處理、分發事件(事件沒有被取消或中斷):在這一步會透過遞迴呼叫,嘗試找到處理事件的 View

這裡的遞迴操作,就是遍歷二元樹的操作

關注變數當前數值
actionMaskedMotionEvent.ACTION_DOWN
mFirstTouchTargetnull
canceledfalse
interceptedfalse

// ViewGroup.java

// ViewGroup 的 ChildView 數量
private int mChildrenCount;    

// ChildView 聚集
private View[] mChildren;

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

    ...

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

        if (!canceled && !intercepted) {    // 成立

            // 檢查事件是否有需要分配到其他 View 上
            View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                    ? findChildWithAccessibilityFocus() : null;

            if (actionMasked == MotionEvent.ACTION_DOWN    // 成立
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { // 多指觸控

                // 如果是 ACTION_DOWN 的話就是 0
                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.
                removePointersFromTouchTargets(idBitsToAssign);

                // 取得目前 ChildeView 的數量
                final int childrenCount = mChildrenCount;
                if (newTouchTarget == null && childrenCount != 0) {// 成立
                    final float x =
                            isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
                    final float y =
                            isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
                    // 由上到下找尋可以接收該事件的 ChildView
                    final ArrayList<View> preorderedList = buildTouchDispatchChildList();


                    final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();

                    final View[] children = mChildren;
                    // 跌代所有 ChildView
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final int childIndex = getAndVerifyPreorderedIndex(
                                childrenCount, i, customOrder);
                        final View child = getAndVerifyPreorderedView(
                                preorderedList, children, childIndex);

                        ...

                        // canReceivePointerEvents,ChildView 無法收到該事件
                        // isTransformedTouchPointInView,ChildView 無法接都到該事件
                        if (!child.canReceivePointerEvents()
                                || !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);

                        // 往下分發事件,這邊如果 ChildView 
                        // 1. ViewGroup: 遞迴呼叫 dispatchTransformedTouchEvent 方法
                        // 2. View: 調用 View#dispatchTouchEvent,最終由 onTouchEvent 處理
                        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 方法
                            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);

                    } // end for

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

                // 跌代完所有的 ChildView,但沒有 View 處理該事件
                if (newTouchTarget == null && mFirstTouchTarget != null) {

                    // 指定 pointer 給最後添加的 View
                    newTouchTarget = mFirstTouchTarget;
                    while (newTouchTarget.next != null) {
                        newTouchTarget = newTouchTarget.next;
                    }
                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                }
            }
        }
        
    }
}


// 賦予 mFirstTouchTarget
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
    final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
    target.next = mFirstTouchTarget;
    mFirstTouchTarget = target;
    return target;
}

E. 判斷事件是否已經分發到目標 ChildView,1. 如果沒有 ChildView 處理,則拋回到 ViewGroup 的 View、2. 處理 cancel 事件

關注變數當前數值
actionMaskedMotionEvent.ACTION_DOWN
mFirstTouchTarget有可能為 null (如果是 ChildView 處理就不是 null)
canceledfalse
interceptedfalse

// ViewGroup.java

// ViewGroup 的 ChildView 數量
private int mChildrenCount;    

// ChildView 聚集
private View[] mChildren;

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

    ...

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

        if (!canceled && !intercepted) {    // 成立

            ... 結束事件分發
        }

        // 目前沒有 View 處理事件
        if (mFirstTouchTarget == null) {
            // 視為普通視圖 (最終會傳遞到當前 ViewGroup 的 View)
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
            // 分發到點擊的 View,如果已經分發,有需要的排除其他點擊目標
            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;
                    }
                    
                    // 如果是 Cacncel 事件則轉移 mFirstTouchTarget
                    if (cancelChild) {
                        if (predecessor == null) {
                            mFirstTouchTarget = next;
                        } else {
                            predecessor.next = next;
                        }
                        target.recycle();
                        target = next;
                        continue;
                    }
                }
                predecessor = target;
                target = next;
            }
        }

        ...
    }

    ...
    return handled;
}

ViewGroup 事件傳遞 dispatchTransformedTouchEvent

● 上面介紹了 ViewGroup 的事件分法,現在介紹 ViewGroup 的事件傳遞 (+Transformed)

A. 處理 View 的取消事件:如果有就透過 dispatchTouchEvent 方法分發給目標 View


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


    final int oldAction = event.getAction();
    // 判斷事件是否取消
    if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
        event.setAction(MotionEvent.ACTION_CANCEL);
        
        // 沒有目標 View
        if (child == null) {
            // 傳給自身的 ViewGroup 的 View
            handled = super.dispatchTouchEvent(event);
        } else {
            handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        return handled;
    }
    ...

    return handle;
}

B. 計算將被傳遞的 pointer 數量


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

    ...

    // 計算將被傳遞的 pointer 數量
    final int oldPointerIdBits = event.getPointerIdBits();
    final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

    // 如果沒有點,則直接返回
    if (newPointerIdBits == 0) {
        return false;
    }

    ...
}

C. 觸摸數量相同:計算偏移量,再透過 dispatchTouchEvent 方法分發事件給 ChildView

● 偏移量

如果有調用 scrollTo or scrollBy 對 ChildView 進行滾動,就會產生 xy 的偏移量


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

    ...

    // 保存座標轉換後的 MotionEvent
    final MotionEvent transformedEvent;

    // 點擊數量一致
    if (newPointerIdBits == oldPointerIdBits) {
        if (child == null || child.hasIdentityMatrix()) {
            if (child == null) {
                // 呼叫 ViewGroup 的 View
                handled = super.dispatchTouchEvent(event);
            } else {
                // 計算 x,y 偏移量
                final float offsetX = mScrollX - child.mLeft;
                final float offsetY = mScrollY - child.mTop;

                // 設定轉換後的 offset 目標
                event.offsetLocation(offsetX, offsetY);

                // 分發事件 到 ChildView
                handled = child.dispatchTouchEvent(event);

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


觸摸數量不相同*:同上,最終都是透過 dispatchTouchEvent 方法分發事件

D. 觸摸數量不相同:同上,最終都是透過 dispatchTouchEvent 方法分發事件


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

    ...

    // 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());
        }

        // 分發事件
        handled = child.dispatchTouchEvent(transformedEvent);
    }
    ...
    return handled;
}

更多的物件導向設計

物件導向的設計基礎如下,如果是初學者或是不熟悉的各位,建議可以從這些基礎開始認識,打好基底才能走個更穩(在學習的時候也需要不斷回頭看)!

設計建模 2 大概念- UML 分類、使用

物件導向設計原則 – 6 大原則(一)

物件導向設計原則 – 6 大原則(二)

創建、行為、結構型設計 8 個比較 | 包裝模式 | 最佳實踐

創建模式 Creation Patterns

創建模式 PK

創建模式 - Creation Patterns

結構模式 Structural Patterns

結構模式 PK

結構模式 - Structural Patterns

結構模式專注於「物件之間的組成」,以形成更大的結構。這些模式可以幫助你確保當系統進行擴展或修改時,不會破壞其整體結構。例如,外觀模式、代理模式… 等等,詳細解說請點擊以下連結

Bridge 橋接模式 | 解說實現 | 物件導向設計

Decorate 裝飾模式 | 解說實現 | 物件導向設計

Proxy 代理模式 | 解說實現 | 分析動態代理

Iterator 迭代設計 | 解說實現 | 物件導向設計

Facade 外觀、門面模式 | 解說實現 | 物件導向設計

Adapter 設計模式 | 解說實現 | 物件導向設計

Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

發表迴響