亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Android事件分發(fā)之View事件處理關(guān)鍵及示例分析

 更新時間:2023年02月14日 11:34:07   作者:大胃粥  
這篇文章主要為大家介紹了Android事件分發(fā)之View事件處理關(guān)鍵及示例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

目的

網(wǎng)上已經(jīng)有很多關(guān)于事件分發(fā)的優(yōu)秀文章,為何我還要自己寫?因為別人總結(jié)的畢竟都是別人的,自己親自閱讀源碼不僅會讓自己更懂得原理,也會讓自己記得更清楚,而且也會發(fā)現(xiàn)另一番天地。

View處理事件的關(guān)鍵

由于所以的控件都直接或者間接繼承自View,因此View的事件分發(fā)機制就是最基礎(chǔ)的一環(huán),需要首先掌握其原理。

那么View的事件從哪里來的呢?當(dāng)然是父View(一個ViewGroup)。父View在尋找能處理事件的子View的時候,會調(diào)用子View的dispatchTouchEvent()把事件傳遞給子View,如果子View的dispatchTouchEvent()返回true,代表子View處理了該事件,如果返回flase就代表該子View不處理事件。如果所有子View都不處理該事件,那么就由父View自己處理。

今天我們這篇文章就是來分析View如何處理事件。我們重點關(guān)心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();
        // 當(dāng)窗口被遮擋,是否過濾掉這個觸摸事件
        if (onFilterTouchEventForSecurity(event)) {
            ListenerInfo li = mListenerInfo;
            // 1. 外部監(jiān)聽器處理
            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()處理事件非常簡單,要么交給外部處理,要么自己來處理。只要任何一方處理了,也就相應(yīng)的處理函數(shù)返回true,View.dispatchTouchEvent()就返回true,代表View處理了事件。否則,View.dispatchTouchEvent()返回false,也就是View不處理該事件。

首先它把事件交給外部進行處理。外部處理指的什么呢?它指的就是交給setOnTouchListener()設(shè)置的監(jiān)聽器來處理。如果這個監(jiān)聽器處理時返回true,也就是OnTouchListener.onTouch()方法返回true,View.dispatchTouchEvent()就返回true,也就說明View處理了該事件。否則交給自己來處理,也就是交由onTouchEvent()處理。

當(dāng)然,如果要讓事件監(jiān)聽器來處理,還必須要讓View處于enabled狀態(tài)??梢酝ㄟ^setEnabled()方法來改變View的enabled狀態(tài)。并且可以通過isEnabled()查詢View是否處于enabled狀態(tài)。

當(dāng)外部無法處理時,也就是上面的三個條件有一個不滿足時,就交給View.onTouchEvent()來處理。此時View.onTouchEvent()的返回值就決定了View.dispatchTouchEvent()的返回值。也就是決定了View是否處理該事件。那么,我們來看下View.onTouchEvent()什么時候返回true,什么時候返回false

    public boolean onTouchEvent(MotionEvent event) {
        // 判斷View是否可點擊(點擊/長按)
        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
        // 處理View是disabled狀態(tài)的情況
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            // ...
            return clickable;
        }      
        // 如果有處理代表,就先交給它處理。如果它不處理,就繼續(xù)交給自己處理
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }        
        // 如果可以點擊,最后會返回true,代表處理了View處理了事件
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            // ...
            return true;
        }
        return false;
    }

這太讓人意外了,只要View可以點擊(點擊/長按),就返回true,否則返回false。

忽略觸摸代理(Touch Delegate)和CONTEXT_CLICKABLE的特性,因為不常用,如果遇到了,可以再來查看。

那么,View默認(rèn)可以點擊,長按嗎?當(dāng)然是不能。這需要子View自己去設(shè)置,例如Button在構(gòu)造函數(shù)中就設(shè)置了自己可以點擊。

我們從代碼角度解釋下View默認(rèn)是否可以點擊和長按

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

在View的構(gòu)造函數(shù)中分別接下了android:clickableandroid:longClickable屬性,從默認(rèn)值可以看出,View默認(rèn)是不可點擊和長按的。也就是說View默認(rèn)不處理任何事件。

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

通過這張圖,我們就可以清楚的了解到View.dispatchTouchEvent()在什么情況下返回 true,在什么情況下返回false。也就了解了View在什么情況下處理了事件,在什么情況下不處理事件。

View.onTouchEvent()分析

View事件處理就這么簡單嗎?如果你只關(guān)心事件分發(fā)到哪里,以及誰處理了事件,那么掌握上面的流程就夠了。

但是你是否還有個疑問,View.onTouchEvent()在干啥呢?OK,如果你保持這份好奇心,那么接著往下看。

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

  • 處理點擊事件
  • 處理長按事件
  • 處理View狀態(tài)改變
  • 處理tap事件

處理長按事件

                case MotionEvent.ACTION_DOWN:
                    mHasPerformedLongPress = false;
                    // 1. 判斷是否在一個滾動的容器中
                    boolean isInScrollingContainer = isInScrollingContainer();
                    if (isInScrollingContainer) {
                        // 1.1 在滾動容器中
                        // ...
                    } else {
                        // 1.2 不是在滾動容器中
                        // 設(shè)置按下狀態(tài)
                        setPressed(true, x, y);
                        // 檢測長按動作
                        checkForLongClick(0, x, y);
                    }
                    break;

setPressed()方法首先會設(shè)置View為按下狀態(tài), 代碼如下

 mPrivateFlags |= PFLAG_PRESSED;

然后,通過checkForLongClick()來檢測長按動作,這是如何實現(xiàn)呢

    private void checkForLongClick(int delayOffset, float x, float y) {
        if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
            mHasPerformedLongPress = false;
            if (mPendingCheckForLongPress == null) {
                mPendingCheckForLongPress = new CheckForLongPress();
            }
            // 在長按超時的時間點,執(zhí)行一個Runable,也就是CheckForLongPres
            postDelayed(mPendingCheckForLongPress,
                    ViewConfiguration.getLongPressTimeout() - delayOffset);
        }
    }
    private final class CheckForLongPress implements Runnable {
        @Override
        public void run() {
            if (isPressed() && (mParent != null)
                    && mOriginalWindowAttachCount == mWindowAttachCount) {
                // 執(zhí)行長按動作
                if (performLongClick(mX, mY)) {
                    // 如果處理了長按動作,mHasPerformedLongPress為true
                    mHasPerformedLongPress = true;
                }
            }
        }
    }    

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

這是什么意思呢?首先在ACTION_DOWN的時候我檢測到按下的動作,那么在還沒有執(zhí)行ACTION_UP之前,如果按下動作超時了,也就是超過了長按的時間點,那么我會執(zhí)行長按動作performLongClick()。我們現(xiàn)在看下執(zhí)行長按做了哪些事情

    public boolean performLongClick(float x, float y) {
        // 記錄長按的位置
        mLongClickX = x;
        mLongClickY = y;
        // 執(zhí)行長按的動作
        final boolean handled = performLongClick();
        // 重置數(shù)據(jù)
        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. 執(zhí)行長按監(jiān)聽器處理動作
        if (li != null && li.mOnLongClickListener != null) {
            handled = li.mOnLongClickListener.onLongClick(View.this);
        }
        // 2. 如果長按監(jiān)聽器不處理,就顯示上下文菜單
        if (!handled) {
            final boolean isAnchored = !Float.isNaN(x) && !Float.isNaN(y);
            handled = isAnchored ? showContextMenu(x, y) : showContextMenu();
        }
        // 3. 如果處理了長按事件,就執(zhí)行觸摸反饋
        if (handled) {
            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
        }
        return handled;
    } 

有三個動作在長按的時候執(zhí)行

  • 如果調(diào)用過setOnLongClickListener()給View設(shè)置長按事件監(jiān)聽器,那么首先把長按事件交給這個監(jiān)聽器處理。如果這個監(jiān)聽器返回true,代表監(jiān)聽器已經(jīng)處理了長按事件,那么直接執(zhí)行第三步的觸摸反饋,并返回。如果這個監(jiān)聽器返回了false,代表監(jiān)聽沒有處理長按事件,那么就執(zhí)行第二步,交給系統(tǒng)處理。
  • 當(dāng)?shù)谝徊教幚聿涣藭r,系統(tǒng)自己來處理,它會顯示一個上下文菜單。
  • 執(zhí)行觸摸反饋。

如果你不了解什么是上下文菜單(Context Menu)和觸摸反饋(Haptic Feednack),可以自行搜索下。

我們已經(jīng)了解了如果觸發(fā)長按做了哪些動作,但是我們也要記得觸發(fā)長按的時機,那就是從手指按下到抬起的時間要超過長按的超時時間。如果沒有超過這個長按超時時間,在ACTION_UP的時候,系統(tǒng)會怎么做呢?

                case MotionEvent.ACTION_UP:
                    // 處于按下狀態(tài)
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        // 沒有執(zhí)行長按動作
                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // 移除長按動作
                            removeLongPressCallback();
                        }
                    }
                    break;

當(dāng)檢測到ACTION_UP時,如果見到了View處于按下狀態(tài),但是還沒有執(zhí)行長按動作。也就是說,還沒有達(dá)到長按的時間點,手指就抬起了,那么系統(tǒng)就會移除在ACTION_DOWN添加的長按動作,之后長按動作就不會觸發(fā)了。

處理點擊事件

我們先分析了長按事件而沒有分析點擊事件,其實是為了更好的講清楚點擊事件,看代碼

                case MotionEvent.ACTION_DOWN:
                    if (isInScrollingContainer) {
                    } else {
                        // 設(shè)置按下狀態(tài)
                        setPressed(true, x, y);
                    }
                    break;

當(dāng)檢測到ACTION_DOWN事件,首先的給它設(shè)置一個按下標(biāo)記,這個前面說過。然后在沒有達(dá)到長按超時這個時間點前,如果檢測到ACTION_UP事件,那么我們就可以認(rèn)為這是一次點擊事件

                case MotionEvent.ACTION_UP:
                    // 處于按下狀態(tài)
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        // 沒有執(zhí)行長按
                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // 移除長按動作
                            removeLongPressCallback();
                            // focusTaken是在touch mode下有效,現(xiàn)在討論的是簡單的手指觸摸
                            if (!focusTaken) {
                                // 創(chuàng)建點擊事件
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                // 通過Message Queue執(zhí)行點擊事件
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }
                    }
                    break;

Touch Mode模式與D-pad有關(guān),讀者可以查閱官方文檔說明。

有了前面關(guān)于長按事件的知識,這里就非常好理解了。

如果沒有執(zhí)行長按動作,就先移除長按回調(diào),那么以后就不會再執(zhí)行長按動作了。相反,如果已經(jīng)執(zhí)行長按動作,那么就不會執(zhí)行點擊事件。

performClick()用來執(zhí)行點擊事件,那么來看下它做了什么

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

點擊事件的處理非常簡單粗暴,默認(rèn)就不處理,也就是返回false。當(dāng)然,如果你想處理,調(diào)用setOnClickListener()即可。

###處理View狀態(tài)改變

響應(yīng)View狀態(tài)改變的操作都集中在setPressed()方法中,其實我們再進一步思考下,View只對按下和抬起的狀態(tài)進行響應(yīng)

    public void setPressed(boolean pressed) {
        // 1. 判斷是否需要執(zhí)行刷新動作
        final boolean needsRefresh = pressed != ((mPrivateFlags & PFLAG_PRESSED) == PFLAG_PRESSED);
        // 2. 設(shè)置狀態(tài)
        if (pressed) {
            // 設(shè)置按下狀態(tài)
            mPrivateFlags |= PFLAG_PRESSED;
        } else {
            // 取消按下狀態(tài)
            mPrivateFlags &= ~PFLAG_PRESSED;
        }
        // 3. 如果需要刷新就刷新View管理的Drawable狀態(tài)
        if (needsRefresh) {
            refreshDrawableState();
        }
        // 4. 如果是ViewGroup,就需要把這個狀態(tài)分發(fā)給子View
        dispatchSetPressed(pressed);
    }

如果手指按下了,會調(diào)用setPressed(true),如果手指抬起了,會調(diào)用setPressed(false)。

假設(shè)我們手指剛按下,那么就需要執(zhí)行第三步的刷新Drawable狀態(tài)的動作

    public void refreshDrawableState() {
        // 標(biāo)記Drawable狀態(tài)需要刷新
        mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
        // 執(zhí)行Drawable狀態(tài)改變的動作
        drawableStateChanged();
        // 通知父View,子View的Drawable狀態(tài)改變了
        ViewParent parent = mParent;
        if (parent != null) {
            parent.childDrawableStateChanged(this);
        }
    }

首先設(shè)置PFLAG_DRAWABLE_STATE_DIRTY標(biāo)記,表示Drawable狀態(tài)需要更新,然后調(diào)用drawableStateChange()來執(zhí)行Drawable狀態(tài)改變動作

    @CallSuper
    protected void drawableStateChanged() {
        // 1. 獲取Drawable新狀態(tài)
        final int[] state = getDrawableState();
        boolean changed = false;
        // 2. 為View管理的各種Drawable設(shè)置新狀態(tài)
        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設(shè)置新狀態(tài),從而改變Drawable
        if (mStateListAnimator != null) {
            mStateListAnimator.setState(state);
        }
        // 4. 如果有Drawable狀態(tài)更新了,就重繪
        if (changed) {
            invalidate();
        }
    }

既然我們要給Drawable更新狀態(tài),那么就的獲取新的狀態(tài)值,這就是第一步所做的事情,我們來看下getDrawableState()如何獲取新狀態(tài)的

    public final int[] getDrawableState() {
        // 如果Drawable狀態(tài)沒有改變,就直接返回之前的狀態(tài)值
        if ((mDrawableState != null) && ((mPrivateFlags & PFLAG_DRAWABLE_STATE_DIRTY) == 0)) {
            return mDrawableState;
        } 
        // 如果狀態(tài)值不存在,或者Drawable狀態(tài)需要更新
        else {
            // 創(chuàng)建狀態(tài)值
            mDrawableState = onCreateDrawableState(0);
            // 重置PFLAG_DRAWABLE_STATE_DIRTY狀態(tài)
            mPrivateFlags &= ~PFLAG_DRAWABLE_STATE_DIRTY;
            return mDrawableState;
        }
    }

剛剛,我們設(shè)置了PFLAG_DRAWABLE_STATE_DIRTY,標(biāo)志著Drawable狀態(tài)需要更新,因此這里會調(diào)用onCreateDrawableState()來獲取

    protected int[] onCreateDrawableState(int extraSpace) {
        // 默認(rèn)是沒有設(shè)置DUPLICATE_PARENT_STATE狀態(tài)
        if ((mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE &&
                mParent instanceof View) {
            return ((View) mParent).onCreateDrawableState(extraSpace);
        }
        int[] drawableState;
        // 2. 根據(jù)各種flag, 獲取狀態(tài)
        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. 把狀態(tài)值轉(zhuǎn)化為一個數(shù)組
        drawableState = StateSet.get(viewStateIndex);
        if (extraSpace == 0) {
            return drawableState;
        }
    }

首先根據(jù)各種標(biāo)志位,例如mPrivateFlagsmPrivateFlags2,來獲取狀態(tài)的值,然后根據(jù)狀態(tài)的值獲取一個狀態(tài)的數(shù)組。

我想你一定想直到這個狀態(tài)數(shù)組是咋樣的,我舉個例子,View默認(rèn)是enabled狀態(tài),那么mViewFlags默認(rèn)設(shè)置了ENABLED標(biāo)記,當(dāng)我們手指按下的時候,mPrivateFlags設(shè)置了PFLAG_PRESSED按下狀態(tài)標(biāo)記。如果值選擇這兩個情況來獲取狀態(tài)值,那么viewStateIndex = VIEW_STATE_PRESSED | VIEW_STATE_ENABLED,用二進制表示就是11000。然后通過StateSet.get(viewStateIndex)轉(zhuǎn)化為數(shù)組就是[StateSet.VIEW_STATE_ENABLED, StateSet.VIEW_STATE_PRESSED]。

現(xiàn)在,我們獲取到Drawable新的狀態(tài)值,那么就可以進行drawableStateChanged()函數(shù)的第二步,為各種Drawable設(shè)置新的狀態(tài)值,例如背景Drawable,前景Drawable。這些Drawable根據(jù)這些新的狀態(tài)值,自己判斷是否需要更新Drawable,例如更新顯示的大小,顏色等等。如果更新了Drawable,那么就會返回true,否則返回false。

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

如果大家不了解StateListAnimator,可以網(wǎng)上查閱下它的使用,這樣就可以對View狀態(tài)改變有更深層次的理解。

drawableStateChanged()函數(shù)的第四步,如果有任意Drawable改變了狀態(tài),那么就需要View進行重繪。

處理tap事件

                case MotionEvent.ACTION_DOWN:
                    mHasPerformedLongPress = false;
                    // 1. 判斷View是否在滾動容器中
                    boolean isInScrollingContainer = isInScrollingContainer();
                    if (isInScrollingContainer) {
                        // 標(biāo)記要觸發(fā)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;
    }

通過循環(huán)遍歷父View,并調(diào)用父View的shouldDelayChildPressedState()方法來判斷父View是否是一個滾動容器。

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

當(dāng)View處于一個滾動容器中,并且容器處于滾動中,這個View需要檢測一個tap事件,也就是表示快速點擊。它有個觸發(fā)的超時時間,大小為100ms(長按的觸發(fā)超時時間是500ms),因此只要按下的事件超過100ms, 都算作一次tap事件。那么,我們先來看下觸發(fā)tap事件都做了啥事

    private final class CheckForTap implements Runnable {
        public float x;
        public float y;
        @Override
        public void run() {
            // 先取消tab的標(biāo)記
            mPrivateFlags &= ~PFLAG_PREPRESSED;
            // 設(shè)置按下狀態(tài)
            setPressed(true, x, y);
            // 檢測長按事件
            checkForLongClick(ViewConfiguration.getTapTimeout(), x, y);
        }
    }

當(dāng)觸發(fā)了tap事件,首先取消標(biāo)記,表示tap事件已經(jīng)執(zhí)行。然后,既然已經(jīng)發(fā)生了點擊事件,那么自然要設(shè)置按下狀態(tài)。最后由于tap事件是在長按事件之前觸發(fā),那么當(dāng)tap事件觸發(fā)后,自然要去檢測長按事件是否觸發(fā)。

我們剛剛說到,tap事件觸發(fā)的條件是,在滾動容器中,從手指按下到抬起的時間要過100ms。那么如果在100ms之前抬起了手指,那么會怎么處理呢,我們來看下ACTION_UP的處理邏輯

                case MotionEvent.ACTION_UP:
                    // 判斷tap動作是否已經(jīng)完成
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    // 如果是按下狀態(tài)或者還沒有觸發(fā)tap動作
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        // 1. 如果還沒有觸發(fā)tap動作,就設(shè)置按下狀態(tài)
                        if (prepressed) {
                            setPressed(true, x, y);
                       }
                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // 移除長按回調(diào)
                            removeLongPressCallback();
                            // 2. 執(zhí)行點擊事件
                            if (!focusTaken) {
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }
                        // 3. 移除tap回調(diào)
                        removeTapCallback();
                    }
                    break;

prepressedtrue表示沒有執(zhí)行tap事件,那么當(dāng)檢測到手指抬起時,先設(shè)置按下狀態(tài)。如果連tap都沒執(zhí)行,肯定也不會執(zhí)行長按事件,因此只會執(zhí)行點擊事件。最后,移除長按回調(diào),這樣tap事件就不會再觸發(fā)。

如果tap事件執(zhí)行了呢?只有一點差別,將會在第二步,根據(jù)是否執(zhí)行了長按來決定是否執(zhí)行點擊事件。

總結(jié)

通過本文的分析,我們可以清楚的知道View如何處理父View傳遞過來的事件,也可以清楚知道View在什么時候處理事件,什么時候不處理事件。

另外,本文也對View.onTouchEvent()作出分析,我們可以清楚知道View如何處理點擊事件,如何處理長按事件,如何處理狀態(tài)改變,以及如何處理tap事件。

以上就是Android事件分發(fā)之View事件處理關(guān)鍵及示例分析的詳細(xì)內(nèi)容,更多關(guān)于Android事件分發(fā)View事件處理的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Android 超詳細(xì)SplashScreen入門教程

    Android 超詳細(xì)SplashScreen入門教程

    Android 12正式版即將發(fā)布,有一個非常顯著的視覺變化就是,Android 12強制給所有的App都增加了SplashScreen的功能。是的,即使你什么都不做,只要你的App安裝到了Android 12手機上,都會自動擁有這個新功能
    2022-03-03
  • Android中的RecyclerView新組件初步上手指南

    Android中的RecyclerView新組件初步上手指南

    RecyclerView是Android L版本開始采用的一個組件,被人們認(rèn)為用來代替?zhèn)鹘y(tǒng)的ListView,下面我們就一起來看一下Android中的RecyclerView新組件初步上手指南
    2016-06-06
  • Studio 編譯報錯:compileSdkVersion ''android-24'' requires JDK 1.8 or later to compile.的解決辦法

    Studio 編譯報錯:compileSdkVersion ''android-24'' requires JDK 1.

    今天小編就為大家分享一篇關(guān)于Studio編譯報錯:compileSdkVersion 'android-24' requires JDK 1.8 or later to compile.的解決辦法,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2018-10-10
  • Android通過HTTP協(xié)議實現(xiàn)斷點續(xù)傳下載實例

    Android通過HTTP協(xié)議實現(xiàn)斷點續(xù)傳下載實例

    本篇文章主要介紹了Android通過HTTP協(xié)議實現(xiàn)斷點續(xù)傳下載實例,具有一定的參考價值,感興趣的小伙伴們可以參考一下。
    2017-04-04
  • android實現(xiàn)點擊按鈕切換不同的fragment布局

    android實現(xiàn)點擊按鈕切換不同的fragment布局

    這篇文章主要為大家詳細(xì)介紹了android實現(xiàn)點擊按鈕切換不同的fragment布局,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-12-12
  • Android入門之TableLayout應(yīng)用解析(一)

    Android入門之TableLayout應(yīng)用解析(一)

    這篇文章主要介紹了Android入門之TableLayout應(yīng)用,需要的朋友可以參考下
    2014-08-08
  • Android實現(xiàn)記住密碼小功能

    Android實現(xiàn)記住密碼小功能

    這篇文章主要為大家詳細(xì)介紹了Android實現(xiàn)記住密碼小功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • Android實現(xiàn)地圖軌跡的方法

    Android實現(xiàn)地圖軌跡的方法

    這篇文章主要為大家詳細(xì)介紹了Android實現(xiàn)地圖軌跡的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-04-04
  • 智能指針與弱引用詳解

    智能指針與弱引用詳解

    智能指針有很多實現(xiàn)方式,android 中的sp 句柄類實際上就是google 實現(xiàn)的一種強引用的智能指針。我沒有仔細(xì)看android sp 的實現(xiàn)方式,但其基本原理是固定的,現(xiàn)在我們從一個相對簡單的例子來看智能指針的實現(xiàn)
    2013-09-09
  • Android設(shè)置TextView樣式SpannableString教程

    Android設(shè)置TextView樣式SpannableString教程

    這篇文章主要為大家介紹了Android設(shè)置TextView樣式SpannableString教程示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-02-02

最新評論