Android 事件分發(fā)機(jī)制-試著讀懂每一行源碼-View

在我的代碼中呕缭,有山有水

為了以后能回顧時(shí)更方便斥废,在整個(gè)分析完以后在最上面粗?jǐn)M一個(gè)分析流程。

  • 繼承Button類蚜退,重寫dispatchTouchEvent闰靴,onTouchEvent,onTouch三個(gè)方法

    • 添加打印日志钻注,獲取直觀的方法調(diào)用順序
  • 根據(jù)日志順序逐個(gè)解析方法蚂且,分析從進(jìn)入dispatchTouchEvent開(kāi)始一直到onTouchEvent中的觸發(fā)ACTION_UP的整個(gè)流程

    • 最終邏輯都在onTouchEvent中
      • ACTION_DOWN 按下時(shí)對(duì)狀態(tài)的保存,以及開(kāi)啟事件處理線程
      • ACTION_MOVE 移動(dòng)狀態(tài)時(shí)對(duì)事件中斷判斷幅恋,以及取消事件處理線程
      • ACTION_UP 抬起時(shí)杏死,對(duì)于onLongClick 、onClick回掉判斷捆交。
  • 收獲:

    • onTouch 和onTouchEvent的關(guān)系
    • onLongClick如何觸發(fā)
    • onClick 如何觸發(fā)
    • onLongClick 和onClick的關(guān)系
    • 如何處理View的點(diǎn)擊
    • 如何攔截事件自定義處理

一 淑翼、繼承Button 重寫事件處理相關(guān)的方法

  • dispatchTouchEvent
  • onTouchEvent方法
  • 為View添加onTouch事件

按下Button 稍做滑動(dòng),觸發(fā)ACTION_DOWN零渐、ATION_MOVE窒舟、ACTION_UP三個(gè)事件

日志:
DOWN 事件
D/TButton: dispatchTouchEvent = ACTON_DOWN
D/MainActivity: onTouch = ACTON_DOWN
D/TButton: onTouchEvent = ACTON_DOWN

MOVE 事件
D/TButton: dispatchTouchEvent = ACTION_MOVE
D/MainActivity: onTouch = ACTION_MOVE
D/TButton: onTouchEvent = ACTION_MOVE

UP 事件
D/TButton: dispatchTouchEvent = ACTION_UP
D/MainActivity: onTouch = ACTION_UP
D/TButton: onTouchEvent = ACTION_UP

日志結(jié)論:三個(gè)方法的調(diào)用順序?yàn)?/strong>
1. dipatchTouchEvent

2. onTouch

3. onTouchEvent


二、 按順序進(jìn)源碼分析

  • dipatchTouchEvent 方法

//先看方法注解诵盼。任何第一次見(jiàn)到的方法都應(yīng)該先看注解
 /**
     * Pass the touch screen motion event down to the target view, or this
     * view if it is the target.
     //通過(guò)觸摸屏幕移動(dòng)到目標(biāo)View,或者當(dāng)前View就是目標(biāo)View
     //目標(biāo)View:屏幕上顯示的View都算
     * @param event The motion event to be dispatched.
     * @return True if the event was handled by the view, false otherwise.
       //如果當(dāng)前View處理了此事件返回true,否則返回false
     */
    public boolean dispatchTouchEvent(MotionEvent event) {
        // If the event should be handled by accessibility focus first.
        //如果此event作為第一個(gè)可訪問(wèn)的焦點(diǎn)被處理
        if (event.isTargetAccessibilityFocus()) {
            // We don't have focus or no virtual descendant has it, do not handle the event.
           //我們沒(méi)有焦點(diǎn)或者沒(méi)有虛擬子類持有焦點(diǎn)惠豺,則不處理此事件
            if (!isAccessibilityFocusedViewOrHost()) {
                return false;
                //這里直接返回false,不處理此事件银还。這里一直出現(xiàn)一個(gè)詞 focus.
                //也就是說(shuō),target View 獲取不到焦點(diǎn)(我們將focusable = false) 將直接跳過(guò)此次事件處理洁墙,他還是能獲取到觸摸事件蛹疯,只是跳過(guò)處理
            }
            // We have focus and got the event, then use normal event dispatch.
            //有焦點(diǎn)并且獲取了此事件,則使用默認(rèn)事件調(diào)度热监。呃-就是有焦點(diǎn)并且接收到了事件-就往下處理
            event.setTargetAccessibilityFocus(false);
        }
        //先設(shè)置一個(gè)返回捺弦,默認(rèn)= false 不消費(fèi)。
        boolean result = false;
       
       //判斷是否是鍵盤輸入事件孝扛,如果先傳遞給輸入事件的onTouchEvent,并且還會(huì)繼續(xù)執(zhí)行后面的代碼
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }
       
       //獲取事件膜-這里不是太明白
        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Defensive cleanup for new gesture
            //防止新手勢(shì)被清楚列吼,停止嵌套滾動(dòng)
            stopNestedScroll();
        }
       
       //這里過(guò)濾判斷里面 是判斷窗口是否被遮擋,如果被遮擋則終止處理返回false
        if (onFilterTouchEventForSecurity(event)) {
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            //下面的邏輯就是判斷是否消費(fèi)此事件
            //1. 是否添加了onTouchlistener,2.是否為enabled狀態(tài)苦始,onTouch中返回true消費(fèi)了此次事件
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
           //上面沒(méi)有沒(méi)有消費(fèi)寞钥,在onTouchEvent中返回為true進(jìn)行消費(fèi)。
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
       
        if (!result && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
       
       //判斷手勢(shì)結(jié)束滾動(dòng)
        // Clean up after nested scrolls if this is the end of 
        // also cancel it if we tried an ACTION_DOWN but wea gesture; 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;
    }

** dispatchTouchEvent方法處理概括**
1 . 首要條件:目標(biāo)View是否有焦點(diǎn)陌选,無(wú)焦點(diǎn)返回false
2 . 是否添加了onTouchListener理郑,是否為可用狀態(tài),是否在onTouch中處理咨油。都滿足 返回true 表示已消費(fèi)
3 . 在onTouch沒(méi)有消費(fèi)(result= false)您炉,且選擇在onTouchEvent中進(jìn)行處理,則返回true 表示已消費(fèi)
這就解釋了日志中的處理順序:dispatchTouchEvent → onTouch → onTouchEvent役电。
所以對(duì)View設(shè)置了onTouchListener那么View自己的OnTouchEvent就不會(huì)執(zhí)行了


  • onTouchEvent 方法
    源代碼中順序不方便閱讀赚爵,下面我們給ACTION換一下位置,從上到下按照觸發(fā)順序來(lái)分析
    ACTION_DOWN → ACTION_MOVE → ACTION_UP → ACTION_CANCEL
    先對(duì)方法內(nèi)的代碼進(jìn)行簡(jiǎn)單的閱讀并添加注解宴霸,按照事件觸發(fā)的順序囱晴,逐個(gè)解析

    補(bǔ)充一下,PFLAG_PREPRESSED瓢谢、PFLAG_PRESSED畸写。
    * PFLAG_PREPRESSED:處理長(zhǎng)按事件的標(biāo)識(shí)。在ACTION_DOWN觸發(fā)后氓扛,開(kāi)啟延時(shí)線程處理長(zhǎng)按事件 但是延時(shí)間還未結(jié)束的狀態(tài)
    * PFLAG_PRESSED :view是否被按下枯芬,只在setPressed()方法內(nèi)設(shè)置,判斷view是否被按下
    在onTouchEvent 這兩個(gè)標(biāo)記被大量用于判斷采郎,理解了這兩個(gè)標(biāo)記的意義對(duì)理解整個(gè)處理邏輯會(huì)極大幫助千所。一開(kāi)始光看注釋我也是暈的,后來(lái)讀到后面結(jié)合上下文明白蒜埋。

 /**
     * Implement this method to handle touch screen motion events.
        實(shí)現(xiàn)此方法來(lái)處理屏幕觸摸事件
     * <p>
     * If this method is used to detect click actions, it is recommended that
      the actions be performed by implementing and calling
      如果這個(gè)方法是用來(lái)監(jiān)測(cè)點(diǎn)擊動(dòng)作淫痰,建議通過(guò)調(diào)用實(shí)現(xiàn)此方法來(lái)處理action
     * @return True if the event was handled, false otherwise.
     //事件被處理返回 true 否則返回false
     */
    public boolean onTouchEvent(MotionEvent event) {
        //獲取事件觸發(fā)點(diǎn)的 橫軸坐標(biāo)
        final float x = event.getX();
        //獲取事件觸發(fā)點(diǎn)的 縱坐標(biāo)
        final float y = event.getY();
        //view狀態(tài)標(biāo)記
        final int viewFlags = mViewFlags;
         //獲取 事件類型 
        final int action = event.getAction();
        
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            // A disabled view that is clickable still consumes the touch
             //events, it just doesn't respond to them.
            return (((viewFlags & CLICKABLE) == CLICKABLE
                    || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
        }
        //上面判斷viewFlags 是否被已禁用,如果被禁用并且有可點(diǎn)擊狀態(tài)則消費(fèi)此次事件整份,但不做處理

        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
        //是否由代理執(zhí)行
        
        //進(jìn)入到事件消費(fèi)的邏輯待错,進(jìn)入到這個(gè)if判斷里面籽孙,最終都會(huì)返回true 表示已消費(fèi)此次傳遞的事件
        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
                //判斷是否可點(diǎn),是否可長(zhǎng)按火俄,上下文是否可點(diǎn)
             
            switch (action) {
                case MotionEvent.ACTION_DOWN:
                   //將是否要執(zhí)行長(zhǎng)按 標(biāo)記 設(shè)為 false
                    mHasPerformedLongPress = false;
                 
                    //直接執(zhí)行的ButtonAction處理 消費(fèi)了此次事件-暫時(shí)還不太明白
                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }
                     //確定是否在滾動(dòng)容器內(nèi)   
                    // Walk up the hierarchy to determine if we're inside a scrolling container.
                    boolean isInScrollingContainer = isInScrollingContainer();
                    
                    //view 在滾動(dòng)容器內(nèi)犯建,延遲短期按下的反饋防止這是一個(gè)滾動(dòng)
                    // For views inside a scrolling container, delay the pressed feedback for
                     a short period in case this is a scroll.
                    if (isInScrollingContainer) {
                    //mPrivateFlags 設(shè)置為 PFLAG_PREPRESSED(按下?tīng)顟B(tài)保存)
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        //創(chuàng)建一個(gè)檢查器
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        mPendingCheckForTap.x = event.getX();
                        mPendingCheckForTap.y = event.getY();
                        //發(fā)送一個(gè)延時(shí)為TapTimeout = 100 ms的消息,最終會(huì)進(jìn)入 checkForLongClick()中檢查執(zhí)行長(zhǎng)按
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                         //不再滾動(dòng)容器內(nèi)瓜客,立刻反饋
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true, x, y);
                        //檢查執(zhí)行長(zhǎng)按适瓦,延時(shí)事件為0
                        checkForLongClick(0);
                    }
                    break;
                 case MotionEvent.ACTION_MOVE:
                 //記錄變化的觸摸點(diǎn)坐標(biāo)
                     drawableHotspotChanged(x, y);
                
                        //如果移動(dòng)到了buttons以外的區(qū)域
                     // Be lenient about moving outside of buttons
                     if (!pointInView(x, y, mTouchSlop)) {
                     
                        //移除回掉-里面移除的是CheckForTap,在滾動(dòng)容器內(nèi)的延時(shí)線程
                         // Outside button
                         removeTapCallback();
                         
                         //判斷mPrivateFlags 是否被設(shè)置了PFLAG_PRESSED標(biāo)記
                         if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                              //移除即將執(zhí)行的長(zhǎng)按回掉
                             // Remove any future long press/tap checks
                             removeLongPressCallback();
                             setPressed(false);
                         }
                     }
                     break;          
                case MotionEvent.ACTION_UP:
                    //是否在執(zhí)行長(zhǎng)按點(diǎn)擊事件
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    //已被按下 或者觸發(fā)長(zhǎng)按事件
                    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) {
                            //為了確保用戶能看見(jiàn)按下的狀態(tài)谱仪,我們?cè)僭O(shè)置一次按下的狀態(tài)
                            // 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);
                       }
                        //不是長(zhǎng)按事件玻熙,也沒(méi)有忽略后續(xù)的upEvent
                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            //移除長(zhǎng)按處理回掉 既移除CheckForLongPress線程
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback();
                            //只執(zhí)行一個(gè)點(diǎn)擊動(dòng)作
                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {
                             //使用Runnable 不如直接調(diào)用performClick ,這可以在點(diǎn)擊動(dòng)作開(kāi)始前就更新視圖
                                 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();
                                }
                                  //如果添加PerformClick到線程隊(duì)列,此線程中會(huì)調(diào)用 performClick()
                                  //如果失敗則立即直接調(diào)用performClick()
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                                //這一做地目的一時(shí)不太明白疯攒,希望有同學(xué)為我解惑呀
                            }
                        }

                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }
                        //如果需要處理長(zhǎng)按事件揭芍,啟動(dòng)延時(shí)64ms的線程,同上添加到線程隊(duì)列失敗則立即調(diào)用run方法確保達(dá)到目的
                        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_CANCEL:
                    setPressed(false);
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    break;
            }

            return true;
        }

        return false;
    }

1.ACTION_DOWN
核心代碼:

                   ··· 
                   
                   if (isInScrollingContainer) {
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                   ···
                   
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                   ···
                   
                        checkForLongClick(0);
                    }    
    
    

上面的代碼卸例,主角有兩個(gè):
1.創(chuàng)建、執(zhí)行CheckForTap線程并延遲100ms
2.調(diào)用checkForLongClick()方法

我們先進(jìn)CheckForTap中看看他要做什么

        private final class CheckForTap implements Runnable {
        public float x;
        public float y;
        @Override
        public void run() {
            mPrivateFlags &= ~PFLAG_PREPRESSED;
            setPressed(true, x, y);
            checkForLongClick(ViewConfiguration.getTapTimeout());
        }
    }     

為mPrivateFlags取消PFLAG_PREPRESSED標(biāo)記
發(fā)現(xiàn)最終它也要調(diào)用checkForLongClick()
立刻追進(jìn)這個(gè)方法里,我猜測(cè)是要觸發(fā)長(zhǎng)按了(看名字猜的哈哈)


    private void checkForLongClick(int delayOffset) {
        if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
            mHasPerformedLongPress = false;
            if (mPendingCheckForLongPress == null) {
                mPendingCheckForLongPress = new CheckForLongPress();
            }
            mPendingCheckForLongPress.rememberWindowAttachCount();
            postDelayed(mPendingCheckForLongPress,
                    ViewConfiguration.getLongPressTimeout() - delayOffset);
        }
    }   

不是長(zhǎng)按事件處理肌毅,又套了一層筷转。先將mHasPerformedLongPress再次設(shè)為false,為了確保長(zhǎng)按一定沒(méi)有被觸發(fā)過(guò)
這里又發(fā)送了一個(gè)延時(shí)線程,主角是CheckForLongPress

    private final class CheckForLongPress implements Runnable {
               ···
          @Override
          public void run() {
               ···
                  if (performLongClick()) {
                      mHasPerformedLongPress = true;
                  }
               ···      
          }
      }

讀谷爸的代碼就是爽呀悬而,performLongClick()呜舒,返回還是布爾值,為true 將mHasPerformedLongPress= true.標(biāo)記長(zhǎng)按已被觸發(fā)
其實(shí)看這段代碼我一直在找一個(gè)主角 onLongClick().

       public boolean performLongClick() {
           sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
           boolean handled = false;
           ListenerInfo li = mListenerInfo;
           if (li != null && li.mOnLongClickListener != null) {
               handled = li.mOnLongClickListener.onLongClick(View.this);
           }
           if (!handled) {
               handled = showContextMenu();
           }
           if (handled) {
               performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
           }
           return handled;
       }
        

一眼就找到了主角onLongClickListener笨奠,判斷是否添加了OnLongClickListener袭蝗,如果添加了則執(zhí)行回調(diào)調(diào)用onLongClick方法,并且返回true般婆。
最終返回處理狀態(tài) handled 表示長(zhǎng)按事件是否被消費(fèi)到腥,到此長(zhǎng)按事件處理就結(jié)束了。

但是蔚袍!蛋蛋是!此處應(yīng)該被打板子 - -乡范!
一開(kāi)始有一個(gè)isInScrollingContainer的判斷,進(jìn)入checkForLongClick方法的延時(shí)時(shí)間不一樣啤咽,原本我慣性理解為在滾動(dòng)容器中處理的時(shí)間要比不在
滾動(dòng)容器中的觸發(fā)時(shí)間長(zhǎng)晋辆,還在納悶這么搞的意義何在?多別扭宇整。剛才想起來(lái)了這段代碼:

    postDelayed(mPendingCheckForLongPress,
                       ViewConfiguration.getLongPressTimeout() - delayOffset);   

我好傻瓶佳,這樣把延時(shí)時(shí)間一減最后整個(gè)時(shí)間軸總長(zhǎng)度不就一樣了嗎都是500ms. 靠!不對(duì)直覺(jué)告訴我不是500ms
從滾動(dòng)容器中的邏輯進(jìn)來(lái)鳞青,CheckForTap線程先延時(shí)了100ms才觸發(fā),觸發(fā)后調(diào)用checkForLongClick方法給出延時(shí)補(bǔ)償100ms
那在checkForLongClick 方法中減去延時(shí)補(bǔ)償霸饲,只減去了100ms为朋。
那就是如果在滾動(dòng)容器中觸發(fā)長(zhǎng)按時(shí)間為400ms
而在非滾動(dòng)容器中觸發(fā)長(zhǎng)按時(shí)間為500ms.- - 好像區(qū)別也不大,但是為毛要這么玩呢~(強(qiáng)迫癥)


2.ACTION_MOVE

                    ···
                     if (!pointInView(x, y, mTouchSlop)) {
                        //移除回掉-移除的是CheckForTap贴彼,在滾動(dòng)容器內(nèi)的延時(shí)線程
                         // Outside button
                         removeTapCallback();
                         //判斷mPrivateFlags 是否被設(shè)置了PFLAG_PRESSED標(biāo)記(是否被點(diǎn)擊)潜腻,如果是則說(shuō)明長(zhǎng)按的延時(shí)處理線程已經(jīng)啟動(dòng)
                         則移除長(zhǎng)按回掉線程
                         if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            //移除即將執(zhí)行的長(zhǎng)按回掉
                             // Remove any future long press/tap checks
                             removeLongPressCallback();
                             setPressed(false);
                         }
                     }

這里有兩個(gè)移除回掉的方法要執(zhí)行:
removeTapCallback()
removeLongPressCallback()
結(jié)合上面的分析,這里調(diào)用順序和對(duì)PFLAG_PRESSED的判斷其實(shí)是必然的器仗,暫且先放一下現(xiàn)看看移除了什么

        /**
          移除定時(shí)線程~ 跪死在英語(yǔ)上了融涣。。精钮。
        * Remove the tap detection timer.
        */
       private void removeTapCallback() {
           if (mPendingCheckForTap != null) {
               mPrivateFlags &= ~PFLAG_PREPRESSED;
               removeCallbacks(mPendingCheckForTap);
           }
       }
   

1 . 判斷是否創(chuàng)建了延時(shí)線程CheckForTap
2 . 對(duì)mPrivateFlags取消 PFLAG_PREPRESSED標(biāo)記(還有一個(gè)地方取消此標(biāo)記是在CheckForTap線程執(zhí)行后威鹿,所以上面對(duì)于PFLAG_PREPRESSED的解釋是沒(méi)錯(cuò)的)
3 . 從線程隊(duì)列中移除mPendingCheckForTap,只要沒(méi)有被執(zhí)行就一定會(huì)被取消轨香,也就中斷了長(zhǎng)按事件
既:在滾動(dòng)容器內(nèi)如果再按下后100ms內(nèi)你滑出了view的范圍馬上就會(huì)丟失長(zhǎng)按事件

后面調(diào)用removeLongPressCallback()為什么先判斷mPrivateFlags是否包含PFLAG_PRESSED的標(biāo)記忽你?
因?yàn)椴皇窃跐L動(dòng)容器內(nèi)觸發(fā),或者CheckForTap 已經(jīng)執(zhí)行了臂容,這兩者都會(huì)進(jìn)入CheckForLongPress線程中科雳,也都會(huì)調(diào)用setPressed(true,x,y)
在其中為mViewFlags添加PFLAG_PRESSED標(biāo)記,長(zhǎng)按事件將要被處理。
所以如果mPrivateFlags包含著PFLAG_PRESSED標(biāo)記脓杉,則說(shuō)明CheckForLongPress已被開(kāi)啟糟秘,需要移除CheckForLongPress線程,中斷回掉球散。
既:在非滾動(dòng)容器內(nèi)觸發(fā)長(zhǎng)按尿赚,在500ms內(nèi)移出了view的范圍則會(huì)取消長(zhǎng)按事件的處理


3.ACTION_UP
核心代碼:

                ···
                    //是否將執(zhí)行長(zhǎng)按事件- mPrivateFlags包含PFLAG_PREPRESSED標(biāo)記,長(zhǎng)按的延時(shí)檢測(cè)正在執(zhí)行
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    //已被按下 或者觸發(fā)長(zhǎng)按事件
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        ···
                        //不是長(zhǎng)按事件蕉堰,也沒(méi)有忽略后續(xù)的upEvent
                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                        ···         
                                //移除長(zhǎng)按檢測(cè)線程
                                removeLongPressCallback();
                                
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                  //如果添加PerformClick到線程隊(duì)列凌净,此線程中會(huì)調(diào)用 performClick()
                                  //如果失敗則立即直接調(diào)用performClick()
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                                //這么做地目的一時(shí)不太明白,希望有同學(xué)為我解惑呀
                            }
                        }

                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }
                        //如果需要處理長(zhǎng)按事件屋讶,啟動(dòng)延時(shí)64ms的線程
                        //同上添加到線程隊(duì)列失敗則立即調(diào)用run方法確保達(dá)到目的
                        if (prepressed) {
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            mUnsetPressedState.run();
                        }
                        //移除長(zhǎng)按檢測(cè)(長(zhǎng)按事件還未執(zhí)行)
                        removeTapCallback();
                    }
                 ···
   
  • 首先來(lái)看這兩行代碼冰寻,我覺(jué)這兩行的信息兩最大
    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed)

記得前面只有兩個(gè)地方取消這個(gè)標(biāo)記,一個(gè)是CheckFotTap的延時(shí)線程執(zhí)行時(shí)丑婿,另一個(gè)是在觸摸移除view范圍后
這里觸發(fā)了ACTION_UP,只能是前者性雄。
根據(jù)ACTION_DOWN中的判斷
1.在滾動(dòng)容器中觸發(fā)ACRION_DOWN,在100ms后才會(huì)調(diào)用setPressed(true,x,y),將PFLAG_PRESSED標(biāo)記添加給mViewFlags
所以在100ms 內(nèi)觸發(fā)ACTION_UP prepressed = true
2.在滾動(dòng)容器中觸發(fā)ACTION_DOWN,會(huì)立即調(diào)用setPressed(true,x,y),并開(kāi)啟處理長(zhǎng)按事件回掉的延時(shí)線程
所以在500ms內(nèi)觸發(fā)ACTION_UP 將會(huì)改變 prepressed = true

100ms是非常短的羹奉,人的手指一般都無(wú)法在100ms內(nèi)觸發(fā)秒旋,所以下面的if判斷基本一直是滿足的。

  • 再來(lái)看if (!mHasPerformedLongPress && !mIgnoreNextUpEvent)
    mHasPerformedLongPress什么時(shí)候?yàn)閠rue呢诀拭,還記的上面出現(xiàn)過(guò)的CheckForLongPress線程迁筛,只有在此線程中執(zhí)行preformLongClick()
    返回true時(shí)mHasPerformedLongPress才等于true.如果沒(méi)有設(shè)置長(zhǎng)按監(jiān)聽(tīng)或者在ACTION_DOWN 觸發(fā)后的500ms內(nèi)(在滾動(dòng)容器內(nèi)為400ms)
    觸發(fā)ACTION_UP mHasPerformedLongPress都為false ,會(huì)進(jìn)入這個(gè)if.(注意這里僅時(shí)進(jìn)入此判斷,并沒(méi)對(duì)長(zhǎng)按事件處理有任何干預(yù))

  • 進(jìn)入if里面
    先執(zhí)行了removeLongPressCallback();
    移除CheckForLongPress線程耕挨,也就是說(shuō)這里就中斷了長(zhǎng)按事件的檢測(cè)细卧,長(zhǎng)按事件從這里就結(jié)束了不會(huì)再有回掉尉桩。
    接著創(chuàng)建了PerformClick線程,并添加了添加到消息隊(duì)列失敗判斷贪庙。最終都會(huì)進(jìn)入performClick()方法中.
    進(jìn)入一探究竟

      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;
         }

久違了蜘犁,終于看到onClick.毫無(wú)疑問(wèn)這里就是對(duì)onClickListener監(jiān)聽(tīng)的回掉。

疑問(wèn)止邮?上面調(diào)用了removeLongPressCallback()方法移除了長(zhǎng)按事件的檢測(cè)这橙,那是不是如果執(zhí)行了onClick 長(zhǎng)按事件就不會(huì)執(zhí)行了呢?
當(dāng)然不是导披,只要onLongClick執(zhí)行返回未false onClick就可以被觸發(fā)屈扎。所以他們是可以同時(shí)存在的。
在CheckForLongPress的run方法中是這樣為 mHasPerformedLongPress改變狀態(tài)的

                    if (performLongClick()) {
                        mHasPerformedLongPress = true;
                    }

可以看到撩匕,mHasPerformedLongPress只取決于performLongClick的返回值鹰晨,并不關(guān)心方法內(nèi)是否執(zhí)行onLongClick.
所以只要onLongClikc返回false,mHasPerformedLongPress就已然為false.在ACTION_UP中就滿足
!mHasPerformedLongPress = true 從而會(huì)執(zhí)行onClick

最后還剩兩個(gè)操作
1.創(chuàng)建UnsetPressedState 并最終調(diào)用setPressed(false),取消mViewFlags的PFLAG_PRESSED標(biāo)記,并刷新視圖。
2.執(zhí)行removeTapCallback()移除CheckForTap延時(shí)線程(在100ms內(nèi)觸發(fā)了ACTION_UP,才會(huì)取消)


對(duì)于單個(gè)View整個(gè)事件分發(fā)流程的源碼學(xué)習(xí)就結(jié)束了止毕,收獲還是蠻大的,對(duì)于添加監(jiān)聽(tīng)和點(diǎn)擊事件整個(gè)處理流程已然有了非常清晰的了解模蜡。之前也是看了很多的這方面的博客的
到底不如自己細(xì)細(xì)分析來(lái)的深刻,而且于都源碼一開(kāi)始挺費(fèi)勁的扁凛,但是稍稍摸索就會(huì)發(fā)現(xiàn)其樂(lè)無(wú)窮之后根本停不下來(lái)哩牍。

在學(xué)習(xí)過(guò)程中發(fā)現(xiàn)和知名博主以前記錄源碼稍有差別,因?yàn)楣鹊苍诟侣铩?br> 本篇源碼SDK版本為23.

學(xué)習(xí)源碼地址 :事件分發(fā)機(jī)制的學(xué)習(xí)-View篇

參照博客:Android View 事件分發(fā)機(jī)制 源碼解析 (上)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末令漂,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子丸边,更是在濱河造成了極大的恐慌叠必,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件妹窖,死亡現(xiàn)場(chǎng)離奇詭異纬朝,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)骄呼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門共苛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人蜓萄,你說(shuō)我怎么就攤上這事隅茎。” “怎么了嫉沽?”我有些...
    開(kāi)封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵辟犀,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我绸硕,道長(zhǎng)堂竟,這世上最難降的妖魔是什么魂毁? 我笑而不...
    開(kāi)封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮出嘹,結(jié)果婚禮上席楚,老公的妹妹穿的比我還像新娘。我一直安慰自己税稼,他們只是感情好烦秩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著娶聘,像睡著了一般闻镶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上丸升,一...
    開(kāi)封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天铆农,我揣著相機(jī)與錄音,去河邊找鬼狡耻。 笑死墩剖,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的夷狰。 我是一名探鬼主播岭皂,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼沼头!你這毒婦竟也來(lái)了爷绘?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤进倍,失蹤者是張志新(化名)和其女友劉穎土至,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體猾昆,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡陶因,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了垂蜗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片楷扬。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖贴见,靈堂內(nèi)的尸體忽然破棺而出烘苹,到底是詐尸還是另有隱情,我是刑警寧澤片部,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布螟加,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏捆探。R本人自食惡果不足惜然爆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望黍图。 院中可真熱鬧曾雕,春花似錦、人聲如沸助被。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)揩环。三九已至搔弄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間丰滑,已是汗流浹背顾犹。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留褒墨,地道東北人炫刷。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像郁妈,于是被迫代替她去往敵國(guó)和親浑玛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

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