Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android事件分發機制詳解

Android事件分發機制詳解

編輯:關於Android編程

在開始講述touch事件流程之前,還簡單介紹下TouchEvent,View和ViewGroup。

1. MotionEvent

  • 整個事件分發流程中,會有大量MotionEvent對象,該對象用於記錄所有與移動相關的事件,比如手指觸摸屏幕事件。
  • 一次完整的MotionEvent事件,是從用戶觸摸屏幕到離開屏幕。整個過程的動作序列:ACTION_DOWN(1次) -> ACTION_MOVE(N次) -> ACTION_UP(1次),
  • 多點觸摸,每一個觸摸點Pointer會有一個id和index。對於多指操作,通過pointerindex來獲取指定Pointer的觸屏位置。比如,對於單點操作時獲取x坐標通過getX(),而多點操作獲取x坐標通過getX(pointerindex)

2. View

  • View是所有視圖對象的父類,實現了動畫相關的接口Drawable.Callback, 按鍵相關的接口KeyEvent.Callback, 交互相關的接口AccessibilityEventSource。比如Button繼承自View。
  • TouchEvent事件處理相關的方法:
    • dispatchTouchEvent(MotionEvent event)
    • onTouchEvent(MotionEvent event)

3. ViewGroup

  • ViewGroup,是一個abstract類,一組View的集合,可以包含View和ViewGroup,是所有布局的父類或間接父類。繼承了View,實現了ViewParent(用於與父視圖交互的接口), ViewManager(用於添加、刪除、更新子視圖到Activity的接口)。比如常用的LinearLayout,RelativeLayout都是繼承自ViewGroup。
  • TouchEvent事件處理相關的方法:
    • dispatchTouchEvent(MotionEvent event)
    • onInterceptTouchEvent(MotionEvent ev)
    • onTouchEvent(MotionEvent event)

4. Activity

  • 4.1 Activity是Android四大基本組件之一,這裡只介紹Activity與touch相關的方法。當手指觸摸到屏幕時,屏幕硬件一行行不斷地掃描每個像素點,獲取到觸摸事件後,從底層產生中斷上報。再通過native層調用Java層InputEventReceiver中的dispatchInputEvent方法。經過層層調用,交由Activity的dispatchTouchEvent方法來處理。
  • 4.2 TouchEvent事件處理相關的方法:
    • dispatchTouchEvent(MotionEvent event)
    • onTouchEvent(MotionEvent event)

流程圖

當用戶觸摸屏幕時會產生一系列MotionEvent,那麼Activity,ViewGroup,View之間是如何處理呢?先通過一張圖來從全局性描述整個流程,後面再詳細介紹。

touch

結論

下面先給出通過對源碼分析的一些結論,並在後面段落的源碼分析中,詳細分析了整個過程,並解釋了結論。

  1. onInterceptTouchEvent返回值true表示事件攔截, onTouch/onTouchEvent 返回值true表示事件消費。

  2. 觸摸事件先交由Activity.dispatchTouchEvent。再一層層往下分發,當中間的ViewGroup都不攔截時,進入最底層的View後,開始由最底層的OnTouchEvent來處理,如果一直不消費,則最後返回到Activity.OnTouchEvent

  3. ViewGroup才有onInterceptTouchEvent攔截方法。在分發過程中,中間任何一層ViewGroup都可以直接攔截,則不再往下分發,而是交由發生攔截操作的ViewGroup的OnTouchEvent來處理。

  4. 子View可調用requestDisallowInterceptTouchEvent方法,來設置disallowIntercept=true,從而阻止父ViewGroup的onInterceptTouchEvent攔截操作。

  5. OnTouchEvent由下往上冒泡時,當中間任何一層的OnTouchEvent消費該事件,則不再往上傳遞,表示事件已處理。

  6. 如果View沒有消費ACTION_DOWN事件,則之後的ACTION_MOVE等事件都不會再接收。

  7. 只要View.onTouchEvent是可點擊或可長按,則消費該事件.

  8. onTouch優先於onTouchEvent執行,上面流程圖中省略,onTouch的位置在onTouchEvent前面。當onTouch返回true,則不執行onTouchEvent,否則會執行onTouchEvent。onTouch只有View設置了OnTouchListener,且是enable的才執行該方法。


源碼分析

1. Activity

####1.1 dispatchTouchEvent

這是Touch事件傳遞到上層的第一個處理方法,是由觸屏事件調用。如果重寫該方法,則會在分發事件之前攔截所有的觸摸事件,源碼如下:

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        //第一次按下操作時,用戶希望能與設備進行交互,可通過實現該方法
        onUserInteraction();
    }

    //獲取當前Activity的頂層窗口是PhoneWindow
    if (getWindow().superDispatchTouchEvent(ev)) { //見代碼(1-1-1)
        return true;
    }
    //當沒有任何view處理時,交由activity的onTouchEvent處理
    return onTouchEvent(ev); // 見代碼(1.2)
}

(1-1-1)

public boolean superDispatchTouchEvent(KeyEvent event) {
    return mDecor.superDispatcTouchEvent(event); // 見代碼(2.1)
}

PhoneWindow的最頂View是DecorView,再交由DecorView處理。而DecorView的間距父類是ViewGroup,接著調用 ViewGroup.dispatchTouchEvent()方法。為了精簡篇幅,有些中間函數調用不涉及關鍵邏輯,可能會直接跳過。

####1.2 onTouchEvent

public boolean onTouchEvent(MotionEvent event) {
    //當窗口需要關閉時,消費掉當前event
    if (mWindow.shouldCloseOnTouch(this, event)) {
        finish();
        return true;
    }

    return false;
}

2. ViewGroup

####2.1 dispatchTouchEvent

這是整個touch事件流程中,最核心的方法,涵蓋了大部分的事件分發的邏輯,這個方法由200多行(這麼冗長的方法,閱讀起來很費勁,下面通過分割線把整個方法劃分幾個區域),言歸正傳,下面重點講解。

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

    if (mInputEventConsistencyVerifier != null) {
        //用於Debug,通知verifier來檢查Touch事件
        mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
    }

    if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
        ev.setTargetAccessibilityFocus(false);
    }

    boolean handled = false;
    //根據隱私策略而來決定是否過濾本次觸摸事件
    if (onFilterTouchEventForSecurity(ev)) {  //見代碼(2-1-1)
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;

        //發生ACTION_DOWN事件
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // 取消並清除之前所有的觸摸targets,可能是由於ANR或者其他狀態改變
            cancelAndClearTouchTargets(ev);
            // 重置觸摸狀態
            resetTouchState();
        }

//---------------------------------start--------------------------------------
        // 發生ACTION_DOWN事件或者已經發生過ACTION_DOWN;才進入此區域,主要功能是攔截器
        //只有發生過ACTION_DOWN事件,則mFirstTouchTarget != null;
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            //可通過調用requestDisallowInterceptTouchEvent,不讓父View攔截事件
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            //判斷是否允許調用攔截器
            if (!disallowIntercept) {
                //調用攔截方法
                intercepted = onInterceptTouchEvent(ev); //見代碼(2.2)
                ev.setAction(action);
            } else {
                intercepted = false;
            }
        } else {
            // 當沒有觸摸targets,且不是down事件時,開始持續攔截觸摸。
            intercepted = true;
        }
//----------------------------------end-----------------------------------------

        // 如果已攔截或者某個view正在處理gesture時,開始正常的事件分發
        if (intercepted || mFirstTouchTarget != null) {
            ev.setTargetAccessibilityFocus(false);
        }

        // 檢測取消器.
        final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;

        //更新觸摸targets列表
        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;

//---------------------------------start--------------------------------------
        //不取消事件,同時不攔截事件, 並且是Down事件才進入該區域
        if (!canceled && !intercepted) {

            //把事件分發給所有的子視圖,尋找可以獲取焦點的視圖。
            View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                    ? findChildWithAccessibilityFocus() : null;

            if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                final int actionIndex = ev.getActionIndex(); // down事件等於0
                final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                        : TouchTarget.ALL_POINTER_IDS;

                removePointersFromTouchTargets(idBitsToAssign); //清空早先的觸摸對象

                final int childrenCount = mChildrenCount;
                //第一次down事件,同時子視圖不會空時
                if (newTouchTarget == null && childrenCount != 0) {
                    final float x = ev.getX(actionIndex);
                    final float y = ev.getY(actionIndex);
                    // 從視圖最上層到下層,獲取所有能接收該事件的子視圖
                    final ArrayList<View> preorderedList = buildOrderedChildList(); //見代碼(2-1-2)
                    final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();
                    final View[] children = mChildren;

                    /* 從最底層的父視圖開始遍歷,
                    ** 找尋newTouchTarget,並賦予view與 pointerIdBits;
                    ** 如果已經存在找尋newTouchTarget,說明正在接收觸摸事件,則跳出循環。
                    */
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final int childIndex = customOrder
                                ? getChildDrawingOrder(childrenCount, i) : i;
                        final View child = (preorderedList == null)
                                ? children[childIndex] : preorderedList.get(childIndex);

                        // 如果當前視圖無法獲取用戶焦點,則跳過本次循環
                        if (childWithAccessibilityFocus != null) {
                            if (childWithAccessibilityFocus != child) {
                                continue;
                            }
                            childWithAccessibilityFocus = null;
                            i = childrenCount - 1;
                        }
                        //如果view不可見,或者觸摸的坐標點不在view的范圍內,則跳過本次循環
                        if (!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }

                        newTouchTarget = getTouchTarget(child);
                        // 已經開始接收觸摸事件,並退出整個循環。
                        if (newTouchTarget != null) {
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;
                        }

                        //重置取消或抬起標志位
                        resetCancelNextUpFlag(child);
                        //如果觸摸位置在child的區域內,則把事件分發給子View或ViewGroup
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { //見代碼(2-1-3)
                            // 獲取TouchDown的時間點
                            mLastTouchDownTime = ev.getDownTime();
                            // 獲取TouchDown的Index
                            if (preorderedList != null) {
                                for (int j = 0; j < childrenCount; j++) {
                                    if (children[childIndex] == mChildren[j]) {
                                        mLastTouchDownIndex = j;
                                        break;
                                    }
                                }
                            } else {
                                mLastTouchDownIndex = childIndex;
                            }

                            //獲取TouchDown的x,y坐標
                            mLastTouchDownX = ev.getX();
                            mLastTouchDownY = ev.getY();
                            //添加TouchTarget,則mFirstTouchTarget != null。
                            newTouchTarget = addTouchTarget(child, idBitsToAssign); //見代碼(2-1-4)
                            //表示以及分發給NewTouchTarget
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }
                        ev.setTargetAccessibilityFocus(false);
                    }
                    // 清除視圖列表
                    if (preorderedList != null) preorderedList.clear();
                }

                if (newTouchTarget == null && mFirstTouchTarget != null) {
                    //將mFirstTouchTarget的鏈表最後的touchTarget賦給newTouchTarget
                    newTouchTarget = mFirstTouchTarget;
                    while (newTouchTarget.next != null) {
                        newTouchTarget = newTouchTarget.next;
                    }
                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                }
            }
        }
//----------------------------------end-----------------------------------------

//---------------------------------start--------------------------------------
        // mFirstTouchTarget賦值是在通過addTouchTarget方法獲取的;
        // 只有處理ACTION_DOWN事件,才會進入addTouchTarget方法。
        // 這也正是當View沒有消費ACTION_DOWN事件,則不會接收其他MOVE,UP等事件的原因
        if (mFirstTouchTarget == null) {
            //沒有觸摸target,則由當前ViewGroup來處理
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS); //見代碼(2-1-3)
        } else {
            //如果View消費ACTION_DOWN事件,那麼MOVE,UP等事件相繼開始執行
            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)) { //見代碼(2-1-3)
                        handled = true;
                    }
                    if (cancelChild) {
                        if (predecessor == null) {
                            mFirstTouchTarget = next;
                        } else {
                            predecessor.next = next;
                        }
                        target.recycle();
                        target = next;
                        continue;
                    }
                }
                predecessor = target;
                target = next;
            }
        }
//----------------------------------end-----------------------------------------

        //當發生抬起或取消事件,更新觸摸targets
        if (canceled
                || actionMasked == MotionEvent.ACTION_UP
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            resetTouchState();
        } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
            final int actionIndex = ev.getActionIndex();
            final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
            removePointersFromTouchTargets(idBitsToRemove);
        }
    } //此處大括號,是if (onFilterTouchEventForSecurity(ev))的結尾

    //通知verifier由於當前時間未處理,那麼該事件其余的都將被忽略
    if (!handled && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
    }
    return handled;
}

(2-1-1)

public boolean onFilterTouchEventForSecurity(MotionEvent event) {
    //noinspection RedundantIfStatement
    if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
            && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
        // Window is obscured, drop this touch.
        return false;
    }
    return true;
} 根據隱私策略來過濾觸摸事件。當返回true,表示繼續分發事件;當返回flase,表示該事件應該被過濾掉,不再進行任何分發。

(2-1-2)

ArrayList<View> buildOrderedChildList() {
    final int count = mChildrenCount;
    if (count <= 1 || !hasChildWithZ()) return null;

    if (mPreSortedChildren == null) {
        mPreSortedChildren = new ArrayList<View>(count);
    } else {
        mPreSortedChildren.ensureCapacity(count);
    }

    final boolean useCustomOrder = isChildrenDrawingOrderEnabled();
    for (int i = 0; i < mChildrenCount; i++) {
        // 添加下一個子視圖到列表
        int childIndex = useCustomOrder ? getChildDrawingOrder(mChildrenCount, i) : i;
        View nextChild = mChildren[childIndex];
        float currentZ = nextChild.getZ(); //見代碼(2-1-2-1)

        int insertIndex = i;
        //按Z軸,從小到大排序所有的子視圖
        while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) {
            insertIndex--;
        }
        mPreSortedChildren.add(insertIndex, nextChild);
    }
    return mPreSortedChildren;
} 獲取一個視圖組的先序列表,通過虛擬的Z軸來排序。

(2-1-2-1)

public float getZ() {
    return getElevation() + getTranslationZ();
}

getZ()用於獲取Z軸坐標。屏幕只有x,y坐標,而Z是虛擬的,可通過setElevation(),setTranslationZ()或者setZ()方法來修改Z軸的坐標值。

(2-1-3)

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);
        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else {
            handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        return handled;
    }

    final int oldPointerIdBits = event.getPointerIdBits();
    final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

    //由於某些原因,發生不一致的操作,那麼將拋棄該事件
    if (newPointerIdBits == 0) {
        return false;
    }

//---------------------------------start--------------------------------------
    //分發的主要區域
    final MotionEvent transformedEvent;
    //判斷預期的pointer id與事件的pointer id是否相等
    if (newPointerIdBits == oldPointerIdBits) {
        if (child == null || child.hasIdentityMatrix()) {
            if (child == null) {
                //不存在子視圖時,ViewGroup調用View.dispatchTouchEvent分發事件,再調用ViewGroup.onTouchEvent來處理事件
                handled = super.dispatchTouchEvent(event);  // 見代碼(3.1)
            } else {
                final float offsetX = mScrollX - child.mLeft;
                final float offsetY = mScrollY - child.mTop;
                event.offsetLocation(offsetX, offsetY);
                //將觸摸事件分發給子ViewGroup或View;
                //如果是ViewGroup,則調用代碼(2.1);
                //如果是View,則調用代碼(3.1);
                handled = child.dispatchTouchEvent(event);

                event.offsetLocation(-offsetX, -offsetY); //調整該事件的位置
            }
            return handled;
        }
        transformedEvent = MotionEvent.obtain(event); //拷貝該事件,來創建一個新的MotionEvent
    } else {
        //分離事件,獲取包含newPointerIdBits的MotionEvent
        transformedEvent = event.split(newPointerIdBits);
    }

    if (child == null) {
        //不存在子視圖時,ViewGroup調用View.dispatchTouchEvent分發事件,再調用ViewGroup.onTouchEvent來處理事件
        handled = super.dispatchTouchEvent(transformedEvent); //見代碼(3.1)
    } else {
        final float offsetX = mScrollX - child.mLeft;
        final float offsetY = mScrollY - child.mTop;
        transformedEvent.offsetLocation(offsetX, offsetY);
        if (! child.hasIdentityMatrix()) {
            //將該視圖的矩陣進行轉換
            transformedEvent.transform(child.getInverseMatrix());
        }
        //將觸摸事件分發給子ViewGroup或View;
        /如果是ViewGroup,則調用代碼(2.1);
        //如果是View,則調用代碼(3.1);
        handled = child.dispatchTouchEvent(transformedEvent);
    }
//----------------------------------end-----------------------------------------

    //回收transformedEvent
    transformedEvent.recycle();
    return handled;
}

該方法是ViewGroup真正處理事件的地方,分發子View來處理事件,過濾掉不相干的pointer ids。當子視圖為null時,MotionEvent將會發送給該ViewGroup。最終調用View.dispatchTouchEvent方法來分發事件。

(2-1-4)

private TouchTarget addTouchTarget(View child, int pointerIdBits) {
    TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
    target.next = mFirstTouchTarget;
    mFirstTouchTarget = target;
    return target;
}

調用該方法,獲取了TouchTarget,同時mFirstTouchTarget不再為null。

####2.2 onInterceptTouchEvent

當返回true,表示該事件被當前視圖攔截;當返回false,繼續執行事件分發。如果在Activity中直接攔截了,那麼將事件不會再往其他View分發,而是由Activity.onTouchEvent()方法處理。

public boolean onInterceptTouchEvent(MotionEvent ev) {
    return false;
}

3. View

#### 3.1 dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent event) {
    if (event.isTargetAccessibilityFocus()) {
        if (!isAccessibilityFocusedViewOrHost()) {
            return false;
        }
        event.setTargetAccessibilityFocus(false);
    }

    boolean result = false;

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

    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        //在Down事件之前,如果存在滾動操作則停止。不存在則不進行操作
        stopNestedScroll();
    }

//---------------------------------start--------------------------------------
    // 該區域說明了mOnTouchListener.onTouch優先於onTouchEvent。
    if (onFilterTouchEventForSecurity(event)) {
        //當存在OnTouchListener,且視圖狀態為ENABLED時,調用onTouch()方法
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            //如果已經消費事件,則返回True
            result = true;
        }
        //如果OnTouch()沒有消費Touch事件則調用OnTouchEvent()
        if (!result && onTouchEvent(event)) { // 見代碼(2.6)
            //如果已經消費事件,則返回True
            result = true;
        }
    }
//----------------------------------end-----------------------------------------

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

    // 處理取消或抬起操作
    if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        stopNestedScroll();
    }

    return result;
}
    1. 先由OnTouchListener的OnTouch()來處理事件,當返回True,則消費該事件,否則進入2。
    1. onTouchEvent處理事件,的那個返回True時,消費該事件。否則不會處理

3.2 onTouchEvent

public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;

//---------------------------------start--------------------------------------
    // 當View狀態為DISABLED,如果可點擊或可長按,則返回True,即消費事件
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
            setPressed(false);
        }
        return (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
    }
//----------------------------------end-----------------------------------------

    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }

//---------------------------------start--------------------------------------
    //當View狀態為ENABLED,如果可點擊或可長按,則返回True,即消費事件;
    //與前面的的結合,可得出結論:只要view是可點擊或可長按,則消費該事件.
    if (((viewFlags & CLICKABLE) == CLICKABLE ||
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
                boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                    // take focus if we don't have it already and we should in
                    // touch mode.
                    boolean focusTaken = false;
                    if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                        focusTaken = requestFocus();
                    }

                    if (prepressed) {
                        // The button is being released before we actually
                        // showed it as pressed.  Make it show the pressed
                        // state now (before scheduling the click) to ensure
                        // the user sees it.
                        setPressed(true, x, y);
                   }

                    if (!mHasPerformedLongPress) {
                        //這是Tap操作,移除長按回調方法
                        removeLongPressCallback();

                        // Only perform take click actions if we were in the pressed state
                        if (!focusTaken) {
                            // Use a Runnable and post this rather than calling
                            // performClick directly. This lets other visual state
                            // of the view update before click actions start.
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            //調用View.OnClickListener
                            if (!post(mPerformClick)) {
                                performClick();
                            }
                        }
                    }

                    if (mUnsetPressedState == null) {
                        mUnsetPressedState = new UnsetPressedState();
                    }

                    if (prepressed) {
                        postDelayed(mUnsetPressedState,
                                ViewConfiguration.getPressedStateDuration());
                    } else if (!post(mUnsetPressedState)) {
                        // If the post failed, unpress right now
                        mUnsetPressedState.run();
                    }

                    removeTapCallback();
                }
                break;

            case MotionEvent.ACTION_DOWN:
                mHasPerformedLongPress = false;

                if (performButtonActionOnTouchDown(event)) {
                    break;
                }

                //獲取是否處於可滾動的視圖內
                boolean isInScrollingContainer = isInScrollingContainer();

                if (isInScrollingContainer) {
                    mPrivateFlags |= PFLAG_PREPRESSED;
                    if (mPendingCheckForTap == null) {
                        mPendingCheckForTap = new CheckForTap();
                    }
                    mPendingCheckForTap.x = event.getX();
                    mPendingCheckForTap.y = event.getY();
                    //當處於可滾動視圖內,則延遲TAP_TIMEOUT,再反饋按壓狀態,用來判斷用戶是否想要滾動。默認延時為100ms
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                } else {
                    //當不再滾動視圖內,則立刻反饋按壓狀態
                    setPressed(true, x, y);
                    //檢測是否是長按
                    checkForLongClick(0);
                }
                break;

            case MotionEvent.ACTION_CANCEL:
                setPressed(false);
                removeTapCallback();
                removeLongPressCallback();
                break;

            case MotionEvent.ACTION_MOVE:
                drawableHotspotChanged(x, y);

                // Be lenient about moving outside of buttons
                if (!pointInView(x, y, mTouchSlop)) {
                    // Outside button
                    removeTapCallback();
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                        // Remove any future long press/tap checks
                        removeLongPressCallback();

                        setPressed(false);
                    }
                }
                break;
        }

        return true;
    }
//----------------------------------end-----------------------------------------

    return false;
}
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved