事件分發之View事件處理

目的

網上已經有不少關於事件分發的優秀文章,爲什麼我還要本身寫?由於別人總結的畢竟都是別人的,本身親自閱讀源碼不只會讓本身更懂得原理,也會讓本身記得更清楚,並且也會發現另外一番天地。java

View處理事件的關鍵

因爲因此的控件都直接或者間接繼承自View,所以View的事件分發機制就是最基礎的一環,須要首先掌握其原理。android

那麼View的事件從哪裏來的呢?固然是父View(一個ViewGroup)。父View在尋找能處理事件的子View的時候,會調用子View的dispatchTouchEvent()把事件傳遞給子View,若是子View的dispatchTouchEvent()返回true,表明子View處理了該事件,若是返回flase就表明該子View不處理事件。若是全部子View都不處理該事件,那麼就由父View本身處理。windows

今天咱們這篇文章就是來分析View如何處理事件。咱們重點關心View.dispatchTouchEvent()啥時候返回true(表明處理了事件),啥時候返回false(表明不處理事件)。數組

View事件處理分析

/** * 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) {
        boolean result = false;

        final int actionMasked = event.getActionMasked();
        
        // 當窗口被遮擋,是否過濾掉這個觸摸事件
        if (onFilterTouchEventForSecurity(event)) {
            ListenerInfo li = mListenerInfo;
            // 1. 外部監聽器處理
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            // 2. 本身處理
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
        return result;
    }
複製代碼

能夠看到View.dispatchTouchEvent()處理事件很是簡單,要麼交給外部處理,要麼本身來處理。只要任何一方處理了,也就相應的處理函數返回trueView.dispatchTouchEvent()就返回true,表明View處理了事件。不然,View.dispatchTouchEvent()返回false,也就是View不處理該事件。app

首先它把事件交給外部進行處理。外部處理指的什麼呢?它指的就是交給setOnTouchListener()設置的監聽器來處理。若是這個監聽器處理時返回true,也就是OnTouchListener.onTouch()方法返回trueView.dispatchTouchEvent()就返回true,也就說明View處理了該事件。不然交給本身來處理,也就是交由onTouchEvent()處理。ide

固然,若是要讓事件監聽器來處理,還必需要讓View處於enabled狀態。能夠經過setEnabled()方法來改變View的enabled狀態。而且能夠經過isEnabled()查詢View是否處於enabled狀態。函數

當外部沒法處理時,也就是上面的三個條件有一個不知足時,就交給View.onTouchEvent()來處理。此時View.onTouchEvent()的返回值就決定了View.dispatchTouchEvent()的返回值。也就是決定了View是否處理該事件。那麼,咱們來看下View.onTouchEvent()何時返回true,何時返回falsepost

public boolean onTouchEvent(MotionEvent event) {
        
        // 判斷View是否可點擊(點擊/長按)
        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
                
        // 處理View是disabled狀態的狀況
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            // ...
            return clickable;
        }      
        
        
        // 若是有處理表明,就先交給它處理。若是它不處理,就繼續交給本身處理
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }        

        
        // 若是能夠點擊,最後會返回true,表明處理了View處理了事件
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            // ...
            return true;
        }

        return false;
    }
複製代碼

這太讓人意外了,只要View能夠點擊(點擊/長按),就返回true,不然返回falsethis

忽略觸摸代理(Touch Delegate)和CONTEXT_CLICKABLE的特性,由於不經常使用,若是遇到了,能夠再來查看。spa

那麼,View默承認以點擊,長按嗎?固然是不能。這須要子View本身去設置,例如Button在構造函數中就設置了本身能夠點擊。

咱們從代碼角度解釋下View默認是否能夠點擊和長按

public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
                // View構造函數中解析android:clickable屬性
                case com.android.internal.R.styleable.View_clickable:
                    // 第二個參數false,表示View默認不可點擊
                    if (a.getBoolean(attr, false)) {
                        viewFlagValues |= CLICKABLE;
                        viewFlagMasks |= CLICKABLE;
                    }
                    break;
                   
                // View構造函數中解析android:longClickable屬性 
                case com.android.internal.R.styleable.View_longClickable:
                    // 第二個參數false,表示View默認不可長按
                    if (a.getBoolean(attr, false)) {
                        viewFlagValues |= LONG_CLICKABLE;
                        viewFlagMasks |= LONG_CLICKABLE;
                    }
                    break;                    
    }
複製代碼

在View的構造函數中分別接下了android:clickableandroid:longClickable屬性,從默認值能夠看出,View默認是不可點擊和長按的。也就是說View默認不處理任何事件。

那麼,咱們用一張圖來解釋View如何處理觸摸事件的

View事件分發

經過這張圖,咱們就能夠清楚的瞭解到View.dispatchTouchEvent()在什麼狀況下返回 true,在什麼狀況下返回false。也就瞭解了View在什麼狀況下處理了事件,在什麼狀況下不處理事件。

View.onTouchEvent()分析

View事件處理就這麼簡單嗎?若是你只關心事件分發到哪裏,以及誰處理了事件,那麼掌握上面的流程就夠了。

可是你是否還有個疑問,View.onTouchEvent()在幹啥呢?OK,若是你保持這份好奇心,那麼接着往下看。

View.onTouchEvent()其實處理了三種狀況

  1. 處理點擊事件
  2. 處理長按事件
  3. 處理View狀態改變
  4. 處理tap事件

處理長按事件

case MotionEvent.ACTION_DOWN:
                    mHasPerformedLongPress = false;
                    
                    // 1. 判斷是否在一個滾動的容器中
                    boolean isInScrollingContainer = isInScrollingContainer();

                    if (isInScrollingContainer) {
                        // 1.1 在滾動容器中
                        // ...
                    } else {
                        // 1.2 不是在滾動容器中
                        // 設置按下狀態
                        setPressed(true, x, y);
                        // 檢測長按動做
                        checkForLongClick(0, x, y);
                    }
                    break;
複製代碼

setPressed()方法首先會設置View爲按下狀態, 代碼以下

mPrivateFlags |= PFLAG_PRESSED;
複製代碼

而後,經過checkForLongClick()來檢測長按動做,這是如何實現呢

private void checkForLongClick(int delayOffset, float x, float y) {
        if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
            mHasPerformedLongPress = false;

            if (mPendingCheckForLongPress == null) {
                mPendingCheckForLongPress = new CheckForLongPress();
            }
            
            // 在長按超時的時間點,執行一個Runable,也就是CheckForLongPres
            postDelayed(mPendingCheckForLongPress,
                    ViewConfiguration.getLongPressTimeout() - delayOffset);
        }
    }
    
    private final class CheckForLongPress implements Runnable {

        @Override
        public void run() {
            if (isPressed() && (mParent != null)
                    && mOriginalWindowAttachCount == mWindowAttachCount) {
                // 執行長按動做
                if (performLongClick(mX, mY)) {
                    // 若是處理了長按動做,mHasPerformedLongPress爲true
                    mHasPerformedLongPress = true;
                }
            }
        }
    }    
複製代碼

其實它是把CheckForLongPress這個Runnable加入到Message Queue中,而後在ViewConfiguration.getLongPressTimeout()這個長按超時的時間點執行。

這是什麼意思呢?首先在ACTION_DOWN的時候我檢測到按下的動做,那麼在尚未執行ACTION_UP以前,若是按下動做超時了,也就是超過了長按的時間點,那麼我會執行長按動做performLongClick()。咱們如今看下執行長按作了哪些事情

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) {

        boolean handled = false;
        final ListenerInfo li = mListenerInfo;
        // 1. 執行長按監聽器處理動做
        if (li != null && li.mOnLongClickListener != null) {
            handled = li.mOnLongClickListener.onLongClick(View.this);
        }
        
        // 2. 若是長按監聽器不處理,就顯示上下文菜單
        if (!handled) {
            final boolean isAnchored = !Float.isNaN(x) && !Float.isNaN(y);
            handled = isAnchored ? showContextMenu(x, y) : showContextMenu();
        }
        
        // 3. 若是處理了長按事件,就執行觸摸反饋
        if (handled) {
            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
        }
        return handled;
    } 
複製代碼

有三個動做在長按的時候執行

  1. 若是調用過setOnLongClickListener()給View設置長按事件監聽器,那麼首先把長按事件交給這個監聽器處理。若是這個監聽器返回true,表明監聽器已經處理了長按事件,那麼直接執行第三步的觸摸反饋,並返回。若是這個監聽器返回了false,表明監聽沒有處理長按事件,那麼就執行第二步,交給系統處理。
  2. 當第一步處理不了時,系統本身來處理,它會顯示一個上下文菜單。
  3. 執行觸摸反饋。

若是你不瞭解什麼是上下文菜單(Context Menu)和觸摸反饋(Haptic Feednack),能夠自行搜索下。

咱們已經瞭解了若是觸發長按作了哪些動做,可是咱們也要記得觸發長按的時機,那就是從手指按下到擡起的時間要超過長按的超時時間。若是沒有超過這個長按超時時間,在ACTION_UP的時候,系統會怎麼作呢?

case MotionEvent.ACTION_UP:
                    // 處於按下狀態
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        // 沒有執行長按動做
                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // 移除長按動做
                            removeLongPressCallback();
                        }

                    }
                    break;
複製代碼

當檢測到ACTION_UP時,若是見到了View處於按下狀態,可是尚未執行長按動做。也就是說,尚未達到長按的時間點,手指就擡起了,那麼系統就會移除在ACTION_DOWN添加的長按動做,以後長按動做就不會觸發了。

處理點擊事件

咱們先分析了長按事件而沒有分析點擊事件,實際上是爲了更好的講清楚點擊事件,看代碼

case MotionEvent.ACTION_DOWN:
                    if (isInScrollingContainer) {

                    } else {
                        // 設置按下狀態
                        setPressed(true, x, y);
                    }
                    break;
複製代碼

當檢測到ACTION_DOWN事件,首先的給它設置一個按下標記,這個前面說過。而後在沒有達到長按超時這個時間點前,若是檢測到ACTION_UP事件,那麼咱們就能夠認爲這是一次點擊事件

case MotionEvent.ACTION_UP:
                    // 處於按下狀態
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        // 沒有執行長按
                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // 移除長按動做
                            removeLongPressCallback();
                            
                            // focusTaken是在touch mode下有效,如今討論的是簡單的手指觸摸
                            if (!focusTaken) {
                                // 建立點擊事件
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                // 經過Message Queue執行點擊事件
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }
                    }
                    break;
複製代碼

Touch Mode模式與D-pad有關,讀者能夠查閱官方文檔說明。

有了前面關於長按事件的知識,這裏就很是好理解了。

若是沒有執行長按動做,就先移除長按回調,那麼之後就不會再執行長按動做了。相反,若是已經執行長按動做,那麼就不會執行點擊事件。

performClick()用來執行點擊事件,那麼來看下它作了什麼

public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            // 1. 首先交給外部的點擊監聽器處理
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            // 2. 若是沒有外部監聽器,就不處理了
            result = false;
        }
        return result;
    }
複製代碼

點擊事件的處理很是簡單粗暴,默認就不處理,也就是返回false。固然,若是你想處理,調用setOnClickListener()便可。

###處理View狀態改變

響應View狀態改變的操做都集中在setPressed()方法中,其實咱們再進一步思考下,View只對按下和擡起的狀態進行響應

public void setPressed(boolean pressed) {
        // 1. 判斷是否須要執行刷新動做
        final boolean needsRefresh = pressed != ((mPrivateFlags & PFLAG_PRESSED) == PFLAG_PRESSED);

        // 2. 設置狀態
        if (pressed) {
            // 設置按下狀態
            mPrivateFlags |= PFLAG_PRESSED;
        } else {
            // 取消按下狀態
            mPrivateFlags &= ~PFLAG_PRESSED;
        }
        
        // 3. 若是須要刷新就刷新View管理的Drawable狀態
        if (needsRefresh) {
            refreshDrawableState();
        }
        
        // 4. 若是是ViewGroup,就須要把這個狀態分發給子View
        dispatchSetPressed(pressed);
    }
複製代碼

若是手指按下了,會調用setPressed(true),若是手指擡起了,會調用setPressed(false)

假設咱們手指剛按下,那麼就須要執行第三步的刷新Drawable狀態的動做

public void refreshDrawableState() {
        // 標記Drawable狀態須要刷新
        mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
        
        // 執行Drawable狀態改變的動做
        drawableStateChanged();

        // 通知父View,子View的Drawable狀態改變了
        ViewParent parent = mParent;
        if (parent != null) {
            parent.childDrawableStateChanged(this);
        }
    }
複製代碼

首先設置PFLAG_DRAWABLE_STATE_DIRTY標記,表示Drawable狀態須要更新,而後調用drawableStateChange()來執行Drawable狀態改變更做

@CallSuper
    protected void drawableStateChanged() {
        // 1. 獲取Drawable新狀態
        final int[] state = getDrawableState();
        boolean changed = false;

        // 2. 爲View管理的各類Drawable設置新狀態
        final Drawable bg = mBackground;
        if (bg != null && bg.isStateful()) {
            changed |= bg.setState(state);
        }

        final Drawable fg = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
        if (fg != null && fg.isStateful()) {
            changed |= fg.setState(state);
        }

        if (mScrollCache != null) {
            final Drawable scrollBar = mScrollCache.scrollBar;
            if (scrollBar != null && scrollBar.isStateful()) {
                changed |= scrollBar.setState(state)
                        && mScrollCache.state != ScrollabilityCache.OFF;
            }
        }

        // 3. 爲StateListAnimator設置新狀態,從而改變Drawable
        if (mStateListAnimator != null) {
            mStateListAnimator.setState(state);
        }
        
        // 4. 若是有Drawable狀態更新了,就重繪
        if (changed) {
            invalidate();
        }
    }
複製代碼

既然咱們要給Drawable更新狀態,那麼就的獲取新的狀態值,這就是第一步所作的事情,咱們來看下getDrawableState()如何獲取新狀態的

public final int[] getDrawableState() {
        // 若是Drawable狀態沒有改變,就直接返回以前的狀態值
        if ((mDrawableState != null) && ((mPrivateFlags & PFLAG_DRAWABLE_STATE_DIRTY) == 0)) {
            return mDrawableState;
        } 
        // 若是狀態值不存在,或者Drawable狀態須要更新
        else {
            // 建立狀態值
            mDrawableState = onCreateDrawableState(0);
            // 重置PFLAG_DRAWABLE_STATE_DIRTY狀態
            mPrivateFlags &= ~PFLAG_DRAWABLE_STATE_DIRTY;
            return mDrawableState;
        }
    }
複製代碼

剛剛,咱們設置了PFLAG_DRAWABLE_STATE_DIRTY,標誌着Drawable狀態須要更新,所以這裏會調用onCreateDrawableState()來獲取

protected int[] onCreateDrawableState(int extraSpace) {
        // 默認是沒有設置DUPLICATE_PARENT_STATE狀態
        if ((mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE &&
                mParent instanceof View) {
            return ((View) mParent).onCreateDrawableState(extraSpace);
        }

        int[] drawableState;
        
        // 2. 根據各類flag, 獲取狀態
        int privateFlags = mPrivateFlags;

        int viewStateIndex = 0;
        if ((privateFlags & PFLAG_PRESSED) != 0) viewStateIndex |= StateSet.VIEW_STATE_PRESSED;
        if ((mViewFlags & ENABLED_MASK) == ENABLED) viewStateIndex |= StateSet.VIEW_STATE_ENABLED;
        if (isFocused()) viewStateIndex |= StateSet.VIEW_STATE_FOCUSED;
        if ((privateFlags & PFLAG_SELECTED) != 0) viewStateIndex |= StateSet.VIEW_STATE_SELECTED;
        if (hasWindowFocus()) viewStateIndex |= StateSet.VIEW_STATE_WINDOW_FOCUSED;
        if ((privateFlags & PFLAG_ACTIVATED) != 0) viewStateIndex |= StateSet.VIEW_STATE_ACTIVATED;
        if (mAttachInfo != null && mAttachInfo.mHardwareAccelerationRequested &&
                ThreadedRenderer.isAvailable()) {
            // This is set if HW acceleration is requested, even if the current
            // process doesn't allow it. This is just to allow app preview
            // windows to better match their app.
            viewStateIndex |= StateSet.VIEW_STATE_ACCELERATED;
        }
        if ((privateFlags & PFLAG_HOVERED) != 0) viewStateIndex |= StateSet.VIEW_STATE_HOVERED;

        final int privateFlags2 = mPrivateFlags2;
        if ((privateFlags2 & PFLAG2_DRAG_CAN_ACCEPT) != 0) {
            viewStateIndex |= StateSet.VIEW_STATE_DRAG_CAN_ACCEPT;
        }
        if ((privateFlags2 & PFLAG2_DRAG_HOVERED) != 0) {
            viewStateIndex |= StateSet.VIEW_STATE_DRAG_HOVERED;
        }
        
        // 2. 把狀態值轉化爲一個數組
        drawableState = StateSet.get(viewStateIndex);

        if (extraSpace == 0) {
            return drawableState;
        }
    }
複製代碼

首先根據各類標誌位,例如mPrivateFlagsmPrivateFlags2,來獲取狀態的值,而後根據狀態的值獲取一個狀態的數組。

我想你必定想直到這個狀態數組是咋樣的,我舉個例子,View默認是enabled狀態,那麼mViewFlags默認設置了ENABLED標記,當咱們手指按下的時候,mPrivateFlags設置了PFLAG_PRESSED按下狀態標記。若是值選擇這兩個狀況來獲取狀態值,那麼viewStateIndex = VIEW_STATE_PRESSED | VIEW_STATE_ENABLED,用二進制表示就是11000。而後經過StateSet.get(viewStateIndex)轉化爲數組就是[StateSet.VIEW_STATE_ENABLED, StateSet.VIEW_STATE_PRESSED]

如今,咱們獲取到Drawable新的狀態值,那麼就能夠進行drawableStateChanged()函數的第二步,爲各類Drawable設置新的狀態值,例如背景Drawable,前景Drawable。這些Drawable根據這些新的狀態值,本身判斷是否須要更新Drawable,例如更新顯示的大小,顏色等等。若是更新了Drawable,那麼就會返回true,不然返回false

drawableStateChanged()函數的第三步,還針對了StateListAnimator的處理。StateListAnimator會根據View狀態值,改變Drawable的顯示。

若是你們不瞭解StateListAnimator,能夠網上查閱下它的使用,這樣就能夠對View狀態改變有更深層次的理解。

drawableStateChanged()函數的第四步,若是有任意Drawable改變了狀態,那麼就須要View進行重繪。

處理tap事件

case MotionEvent.ACTION_DOWN:
                    mHasPerformedLongPress = false;
                    
                    // 1. 判斷View是否在滾動容器中
                    boolean isInScrollingContainer = isInScrollingContainer();

                    if (isInScrollingContainer) {
                        // 標記要觸發tab事件
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        mPendingCheckForTap.x = event.getX();
                        mPendingCheckForTap.y = event.getY();
                        // 2. 若是View在滾動容器中,那麼檢測一個tab動做
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                    
                    }
                    break;
複製代碼

第一步,判斷View是否在一個滾動的容器中

public boolean isInScrollingContainer() {
        ViewParent p = getParent();
        while (p != null && p instanceof ViewGroup) {
            if (((ViewGroup) p).shouldDelayChildPressedState()) {
                return true;
            }
            p = p.getParent();
        }
        return false;
    }
複製代碼

經過循環遍歷父View,並調用父View的shouldDelayChildPressedState()方法來判斷父View是不是一個滾動容器。

那麼什麼樣的ViewGroup是滾動容器呢?例如ScrollView就是一個滾動容器,由於它有讓子View滾動的特性,因此shouldDelayChildPressedState()返回true。而LinearLayout就不是一個滾動容器,它自己沒有設計滾動特性,所以shouldDelayChildPressedState()返回false

當View處於一個滾動容器中,而且容器處於滾動中,這個View須要檢測一個tap事件,也就是表示快速點擊。它有個觸發的超時時間,大小爲100ms(長按的觸發超時時間是500ms),所以只要按下的事件超過100ms, 都算做一次tap事件。那麼,咱們先來看下觸發tap事件都作了啥事

private final class CheckForTap implements Runnable {
        public float x;
        public float y;

        @Override
        public void run() {
            // 先取消tab的標記
            mPrivateFlags &= ~PFLAG_PREPRESSED;
            // 設置按下狀態
            setPressed(true, x, y);
            // 檢測長按事件
            checkForLongClick(ViewConfiguration.getTapTimeout(), x, y);
        }
    }
複製代碼

當觸發了tap事件,首先取消標記,表示tap事件已經執行。而後,既然已經發生了點擊事件,那麼天然要設置按下狀態。最後因爲tap事件是在長按事件以前觸發,那麼當tap事件觸發後,天然要去檢測長按事件是否觸發。

咱們剛剛說到,tap事件觸發的條件是,在滾動容器中,從手指按下到擡起的時間要過100ms。那麼若是在100ms以前擡起了手指,那麼會怎麼處理呢,咱們來看下ACTION_UP的處理邏輯

case MotionEvent.ACTION_UP:
                    // 判斷tap動做是否已經完成
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    
                    // 若是是按下狀態或者尚未觸發tap動做
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        
                        // 1. 若是尚未觸發tap動做,就設置按下狀態
                        if (prepressed) {
                            setPressed(true, x, y);
                       }
                    
                        
                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // 移除長按回調
                            removeLongPressCallback();
                            
                            // 2. 執行點擊事件
                            if (!focusTaken) {
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }
                        
                        // 3. 移除tap回調
                        removeTapCallback();
                    }
                    break;
複製代碼

prepressedtrue表示沒有執行tap事件,那麼當檢測到手指擡起時,先設置按下狀態。若是連tap都沒執行,確定也不會執行長按事件,所以只會執行點擊事件。最後,移除長按回調,這樣tap事件就不會再觸發。

若是tap事件執行了呢?只有一點差異,將會在第二步,根據是否執行了長按來決定是否執行點擊事件。

總結

經過本文的分析,咱們能夠清楚的知道View如何處理父View傳遞過來的事件,也能夠清楚知道View在何時處理事件,何時不處理事件。

另外,本文也對View.onTouchEvent()做出分析,咱們能夠清楚知道View如何處理點擊事件,如何處理長按事件,如何處理狀態改變,以及如何處理tap事件。

相關文章
相關標籤/搜索