Android View的Touch事件分發(fā)

事件分發(fā)的重要性我就不多說了,我們先從簡到難青瀑。
先看ViewTouch事件分發(fā),我自定義一個View衣撬,重寫OnTouchEvent函數(shù),然后分別設(shè)置OnTouchListenerOnClick

自定義重寫OnTouchEvent
布局
設(shè)置onTouchListener和onClick

ACTION_DOWN = 0 ACTION_UP=1 ACTION_MOVE=2
我們我們按下這個View點擊一下:

點擊

可以發(fā)現(xiàn)執(zhí)行的順序是:
OnTouchListener.DOWN -> OnTouchEvent.DOWN -> OnTouchListener.MOVE -> OnTouchEvent.MOVE -> OnTouchListener.UP-> OnTouchEvent.UP-> OnClickListener
從這我們就可以猜想執(zhí)行的優(yōu)先級為
OnTouchListener > onTouchEvent > onClick
接下來我們驗證這個猜想终息,
我們把OnTouchListeneronTouch返回值改為true

OnTouchListener的onTouch返回值改為true

我在點擊一下夺巩,這里大膽猜想一下onTouchEventonClick不會執(zhí)行了,看看執(zhí)行的順序

OnTouchListener的onTouch返回值改為true之后的執(zhí)行順序

這時候執(zhí)行的順序如下:
OnTouchListener.DOWN ->OnTouchListener.MOVE-> OnTouchListener.UP
這里驗證了我的猜想周崭,可以得到如下結(jié)論

ViewTouch事件分發(fā)柳譬,OnToucherListener如果返回true的話,就說明把事件從OnToucherListener這里攔截了续镇,后續(xù)的onTouchEventonClick就收不到事件了美澳。

接下來我們把OnTouchListeneronTouch返回值改為false,讓它不攔截事件,把onTouchEvent返回值改為true

onTouchEvent返回值改為true

OnTouchListener的onTouch返回值改為false

我們點擊一下,猜想是OnTouchListeneronTouchEvent能夠接收到事件摸航,onClick將不會觸發(fā)

和我們想的一致制跟,這時候執(zhí)行順序變?yōu)?
OnTouchListener.DOWN ->OnTouchEvent.DOWN-> OnTouchListener.MOVE -> OnTouchEvent.MOVE->OnTouchListener.UP ->OnTouchEvent.UP
這里我們就可能得到結(jié)論

ViewTouch事件分發(fā),如果OnToucherListener返回false酱虎,onTouchEvent返回true凫岖,就說明把事件從onTouchEvent這里攔截了,onClick就不會觸發(fā)逢净。

通過上面兩個結(jié)論我們驗證了我們的優(yōu)先級猜想

ViewTouch事件分發(fā)哥放,執(zhí)行的優(yōu)先級為OnTouchListener > onTouchEvent > onClick,如果前兩個任意一個地方返回true爹土,那么后續(xù)將不會收到事件甥雕。

接下來我們從源碼的角度分析,首先我們需要知道,你點擊或者或者觸摸任何一個View 都會調(diào)用 dispatchTouchEvent()函數(shù)胀茵,我們就從這里開始分析源碼:

 /**
     * Pass the touch screen motion event down to the target view, or this
     * view if it is the target.
     *
     * @param event The motion event to be dispatched.
     * @return True if the event was handled by the view, false otherwise.
     */
    public boolean dispatchTouchEvent(MotionEvent event) {
        // If the event should be handled by accessibility focus first.
        if (event.isTargetAccessibilityFocus()) {
            // We don't have focus or no virtual descendant has it, do not handle the event.
            if (!isAccessibilityFocusedViewOrHost()) {
                return false;
            }
            // We have focus and got the event, then use normal event dispatch.
            event.setTargetAccessibilityFocus(false);
        }

        boolean result = false;

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

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

        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

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

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

        return result;
    }

我們先要知道ListenerInfo這個是做什么的社露?

static class ListenerInfo {
        /**
         * Listener used to dispatch focus change events.
         * This field should be made private, so it is hidden from the SDK.
         * {@hide}
         */
        protected OnFocusChangeListener mOnFocusChangeListener;
        /**
         * Listeners for layout change events.
         */
        private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners;
        protected OnScrollChangeListener mOnScrollChangeListener;
        /**
         * Listeners for attach events.
         */
        private CopyOnWriteArrayList<OnAttachStateChangeListener> mOnAttachStateChangeListeners;
        /**
         * Listener used to dispatch click events.
         * This field should be made private, so it is hidden from the SDK.
         * {@hide}
         */
        public OnClickListener mOnClickListener;
        /**
         * Listener used to dispatch long click events.
         * This field should be made private, so it is hidden from the SDK.
         * {@hide}
         */
        protected OnLongClickListener mOnLongClickListener;
        /**
         * Listener used to dispatch context click events. This field should be made private, so it
         * is hidden from the SDK.
         * {@hide}
         */
        protected OnContextClickListener mOnContextClickListener;
        /**
         * Listener used to build the context menu.
         * This field should be made private, so it is hidden from the SDK.
         * {@hide}
         */
        protected OnCreateContextMenuListener mOnCreateContextMenuListener;
        private OnKeyListener mOnKeyListener;
        private OnTouchListener mOnTouchListener;
        private OnHoverListener mOnHoverListener;
        private OnGenericMotionListener mOnGenericMotionListener;
        private OnDragListener mOnDragListener;
        private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener;
        OnApplyWindowInsetsListener mOnApplyWindowInsetsListener;
    }

這是一個view所有事件的集合類。接下來進入這段代碼,

            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                result = true;
            }

從這段代碼我們就可以知道如果mOnTouchListener !=null并且當(dāng)前view的是enable=true就會執(zhí)行li.mOnTouchListener.onTouch(this, event),執(zhí)行li.mOnTouchListener.onTouch(this, event)返回的false的話就會執(zhí)行onTouchEvent(event)琼娘。
從這我們就可以知道OnTouchListener的優(yōu)先級大于onTouchEvent峭弟。接著我們點擊onTouchEvent進入

 public boolean onTouchEvent(MotionEvent event) {
           //......代碼太長  省略
            switch (action) {
                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 && !mIgnoreNextUpEvent) {
                            // This is a tap, so remove the longpress check
                            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();
                                }
                                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();
                    }
                    mIgnoreNextUpEvent = false;
                    break;

                case MotionEvent.ACTION_DOWN:
                    mHasPerformedLongPress = false;

                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }

                    // Walk up the hierarchy to determine if we're inside a scrolling container.
                    boolean isInScrollingContainer = isInScrollingContainer();

                    // For views inside a scrolling container, delay the pressed feedback for
                    // a short period in case this is a scroll.
                    if (isInScrollingContainer) {
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        mPendingCheckForTap.x = event.getX();
                        mPendingCheckForTap.y = event.getY();
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true, x, y);
                        checkForLongClick(0, x, y);
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:
                    setPressed(false);
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    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;
        }

        return false;
    }

可以看到附鸽,我們在MotionEvent.ACTION_UP事件里面,經(jīng)過一系列的判斷瞒瘸,然后進入到了performClick()這個函數(shù)

 /**
     * Call this view's OnClickListener, if it is defined.  Performs all normal
     * actions associated with clicking: reporting accessibility event, playing
     * a sound, etc.
     *
     * @return True there was an assigned OnClickListener that was called, false
     *         otherwise is returned.
     */
    public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        return result;
    }

這個函數(shù)很明顯的就知道是執(zhí)行onClick,從這就可以得到如下結(jié)論

onClick事件是在onTouchEventMotionEvent.ACTION_UP事件通過performClick()->li.mOnClickListener.onClick(this)觸發(fā)的坷备。

到這里我們就驗證了我們剛才的優(yōu)先級的結(jié)論。當(dāng)然在onTouchEvent(MotionEvent event源碼中情臭,我們在MotionEvent.ACTION_DOWN里面可以看到長按事件

      case MotionEvent.ACTION_DOWN:
         //...
         checkForLongClick(0, x, y);
         break;

//檢測長按事件
private void checkForLongClick(int delayOffset, float x, float y) {
        if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
            mHasPerformedLongPress = false;

            if (mPendingCheckForLongPress == null) {
                mPendingCheckForLongPress = new CheckForLongPress();
            }
            mPendingCheckForLongPress.setAnchor(x, y);
            mPendingCheckForLongPress.rememberWindowAttachCount();
            postDelayed(mPendingCheckForLongPress,
                    ViewConfiguration.getLongPressTimeout() - delayOffset);
        }
    }

 private final class CheckForLongPress implements Runnable {
        private int mOriginalWindowAttachCount;
        private float mX;
        private float mY;

        @Override
        public void run() {
            if (isPressed() && (mParent != null)
                    && mOriginalWindowAttachCount == mWindowAttachCount) {
              //觸發(fā)長按事件
                if (performLongClick(mX, mY)) {
                    mHasPerformedLongPress = true;
                }
            }
        }

        public void setAnchor(float x, float y) {
            mX = x;
            mY = y;
        }

        public void rememberWindowAttachCount() {
            mOriginalWindowAttachCount = mWindowAttachCount;
        }
    }



public boolean performLongClick(float x, float y) {
        mLongClickX = x;
        mLongClickY = y;
        final boolean handled = performLongClick();
        mLongClickX = Float.NaN;
        mLongClickY = Float.NaN;
        return handled;
    }

public boolean performLongClick() {
        return performLongClickInternal(mLongClickX, mLongClickY);
    }

 private boolean performLongClickInternal(float x, float y) {
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);

        boolean handled = false;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnLongClickListener != null) {
            handled = li.mOnLongClickListener.onLongClick(View.this);
        }
        if (!handled) {
            final boolean isAnchored = !Float.isNaN(x) && !Float.isNaN(y);
            handled = isAnchored ? showContextMenu(x, y) : showContextMenu();
        }
        if (handled) {
            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
        }
        return handled;
    }

從這段代碼我們又可以得到如下結(jié)論

ViewOnLongClickListener是在onTouchEventMotionEvent.ACTION_DOWN事件通過checkForLongClick() ->performLongClick(mX, mY)->performLongClick() ->performLongClickInternal(mLongClickX, mLongClickY) ->li.mOnLongClickListener.onLongClick(View.this)的執(zhí)行順序觸發(fā)的省撑。

這樣ViewOnTouch事件分發(fā)機制就分析得差不多,具體的判斷細節(jié)等還是需要自己查看源碼俯在。

參考鏈接:
http://www.reibang.com/p/98d1895c409d
http://www.reibang.com/p/e99b5e8bd67b

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末竟秫,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子跷乐,更是在濱河造成了極大的恐慌肥败,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,729評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件愕提,死亡現(xiàn)場離奇詭異拙吉,居然都是意外死亡,警方通過查閱死者的電腦和手機揪荣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評論 3 399
  • 文/潘曉璐 我一進店門筷黔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人仗颈,你說我怎么就攤上這事佛舱。” “怎么了挨决?”我有些...
    開封第一講書人閱讀 169,461評論 0 362
  • 文/不壞的土叔 我叫張陵请祖,是天一觀的道長。 經(jīng)常有香客問我脖祈,道長肆捕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,135評論 1 300
  • 正文 為了忘掉前任盖高,我火速辦了婚禮慎陵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘喻奥。我一直安慰自己席纽,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 69,130評論 6 398
  • 文/花漫 我一把揭開白布撞蚕。 她就那樣靜靜地躺著润梯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上纺铭,一...
    開封第一講書人閱讀 52,736評論 1 312
  • 那天寇钉,我揣著相機與錄音,去河邊找鬼舶赔。 笑死扫倡,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的顿痪。 我是一名探鬼主播镊辕,決...
    沈念sama閱讀 41,179評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼油够,長吁一口氣:“原來是場噩夢啊……” “哼蚁袭!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起石咬,我...
    開封第一講書人閱讀 40,124評論 0 277
  • 序言:老撾萬榮一對情侶失蹤揩悄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后鬼悠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體删性,經(jīng)...
    沈念sama閱讀 46,657評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,723評論 3 342
  • 正文 我和宋清朗相戀三年焕窝,在試婚紗的時候發(fā)現(xiàn)自己被綠了蹬挺。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,872評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡它掂,死狀恐怖巴帮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情虐秋,我是刑警寧澤榕茧,帶...
    沈念sama閱讀 36,533評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站客给,受9級特大地震影響用押,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜靶剑,卻給世界環(huán)境...
    茶點故事閱讀 42,213評論 3 336
  • 文/蒙蒙 一蜻拨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧桩引,春花似錦官觅、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春功氨,著一層夾襖步出監(jiān)牢的瞬間序苏,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評論 1 274
  • 我被黑心中介騙來泰國打工捷凄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留忱详,地道東北人。 一個月前我還...
    沈念sama閱讀 49,304評論 3 379
  • 正文 我出身青樓跺涤,卻偏偏與公主長得像匈睁,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子桶错,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,876評論 2 361

推薦閱讀更多精彩內(nèi)容