事件分發(fā)和滑動沖突知識點(diǎn)總結(jié)

View的事件分發(fā)和滑動沖突學(xué)習(xí)總結(jié)

前言

本文分兩個部分,第一部分會先過一遍事件分發(fā)機(jī)制的流程并做一些結(jié)論性的總結(jié),然后從源碼層面分析這些流程。第二部分會介紹滑動沖突問題的一些解決方案。查了比較多的資料,也有一些自己的看法,由于知識有限,差錯之處希望各位不吝指出。

View 的事件分發(fā)機(jī)制

簡介

當(dāng)一個MotionEvent產(chǎn)生了以后,系統(tǒng)需要把這個事件傳遞給一個具體的View,這個傳遞過程就是分發(fā)過程。這個過程由三個方法共同完成:

public boolean dispatchTouchEvent(MotionEvent ev)

這個方法用來進(jìn)行事件的分發(fā),如果事件能夠傳遞給當(dāng)前View,那么這個方法就一定會被調(diào)用。他的返回結(jié)果受到當(dāng)前View的onTouchEvent和下級的dispatchTouchEvent的影響,表示的是當(dāng)前View(包括其子View)是否消耗這個事件。

public boolean onInterceptTouchEvent(MotionEvent event)

這個方法在該ViewGroup的dispatchTouchEvent方法中調(diào)用,用來判斷該View是否攔截某個事件,若攔截,那么該View將直接攔截與該事件同一事件序列的剩余事件,對這一事件序列不再調(diào)用onInterceptTouchEvent判斷是否攔截。返回值表示是否攔截當(dāng)前事件。這個方法在View的子類ViewGroup中而不在View中。

public boolean onTouchEvent(MotionEvent event)

在dispatchTouchEvent中調(diào)用,用來處理點(diǎn)擊事件,返回結(jié)果表示是否消耗當(dāng)前事件,如果不消耗,則不能接受同一事件序列內(nèi)的剩余事件。

圖解--來自Kelin
事件分發(fā)圖解
模擬流程的偽代碼
public boolean dispatchTouchEvent(MontionEvent ev){
    boolean consume = false;
    if(onInterceptTouchEvent(ev)){
        consume = onTouchEvent(ev);
    }else{
        consume = child.dispatchTouchEvent(ev);
    }
    return consume;
}
結(jié)論
  1. 事件序列是指從手指接觸屏幕開始,到手指離開屏幕結(jié)束這個過程中產(chǎn)生的一系列事件,這個事件序列以down開始,中間有數(shù)量不定的move,最終以up結(jié)束。
  2. 某個ViewGroup被判定到攔截某一事件M,那么M所在序列中M之后的事件,都會被這個ViewGroup處理(如果事件能傳遞給它),而且這個ViewGroup不會再調(diào)用其onInterceptTouchEvent方法去判斷是否攔截,而是默認(rèn)攔截。也就是說,onInterceptTouchEvent這個方法并不會總是被調(diào)用。
  3. 如果某個View不消耗ACTION_DOWN事件,那么同一事件序列中的其他事件都不會再給它處理,而是給它的父元素處理。
  4. 如果View不消耗ACTION_DOWN以外的其他事件,那么這個點(diǎn)擊事件會消失,此時父元素的onTouchEvent不會被調(diào)用,并且當(dāng)前View可以持續(xù)接收到后續(xù)事件,消失的事件會傳遞給Activity處理。
  5. ViewGroup默認(rèn)不攔截任何事件。
  6. View沒有onInterceptTouchEvent方法,一旦有事件傳遞給它,它的onTouchEvent方法就會被調(diào)用。
  7. View的onTouchEvent默認(rèn)會消耗事件,除非它是不可點(diǎn)擊的(clickable和longClickable同時為false)。View的longClickable屬性默認(rèn)為false。
  8. 一個disable的View依然可能會消耗事件,只要clickable或longClickablec中有一個為true。但并不運(yùn)行onClickListener的onClick方法和onLongClickListener中的onLongClick方法甚至是onTouchListener中的onTouch方法。
  9. 事件的傳遞過程是由內(nèi)向外的,即事件總是先傳遞給父元素,然后再由父元素分發(fā)給子View,通過requestDisallowInterceptTouchEvent方法可以在子元素中干預(yù)父元素的分發(fā)過程,當(dāng)ACTION_DOWN事件除外。
分析

接下來就該上源碼了,首先是ViewGroup的dispatchTouch方法(分析在注釋里)

 public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }

        // If the event targets the accessibility focused view and this is it, start
        // normal event dispatch. Maybe a descendant is what will handle the click.
        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
            ev.setTargetAccessibilityFocus(false);
        }

        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;
            
            //第一步,進(jìn)行初始化操作
                /*判斷是否為ACTION_DOWN,如果是,
                就將一些標(biāo)志位進(jìn)行重置等操作,包括
                disallowIntercept,所以不能被不允許攔截
                注意,在cancelAndClearTouchTargets(ev)中
                會將mFirstTouchTarget設(shè)置為null*/
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                // Throw away all previous state when starting a new touch gesture.
                // The framework may have dropped the up or cancel event for the previous gesture
                // due to an app switch, ANR, or some other state change.
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            // 第二步,檢查是否攔截
            final boolean intercepted;
                /* 判斷是否為ACTION_DOWN或mFirstTouchTarget是否為null
                當(dāng)滿足其中一個的時候,進(jìn)入并調(diào)用onInterceptTouchEvent(ev)
                來判斷是否攔截。這里需要注意的是,在ViewGroup的子View處理
                事件成功的時候,mFirstTouchTarget會被賦值并指向子元素。
                也就是說,當(dāng)事件不為ACTION_DOWN時,如果想調(diào)用
                onInterceptTouchEvent(ev)判斷是否攔截,
                就必須讓mFirstTouchTarget != null,
                而這個條件必須是前一個事件沒有被攔截且
                ACTION_DWON能被子View消耗(如果ACTION_Down不能被消耗,
                則mFirstTouchTarget是不會被賦值的。而如果是其他事件不
                被消耗,由于子View消耗ACTION_DOWN時對mFirstTouchTarget
                賦了值,所以還是會進(jìn)入調(diào)用onInterceptTouchEvent)。*/
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                /* 如果子View調(diào)用了requestDisallowInterceptTouchEvent方法,
                則disallowIntercept為true,那么除了ACTION_DOWN,其他事件都不允許被攔截*/
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                /*如果不滿足條件,則說明前一個事件被攔截,
                那么剩下的同一序列的事件都會被攔截 */
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }

            // If intercepted, start normal event dispatch. Also if there is already
            // a view that is handling the gesture, do normal event dispatch.
            if (intercepted || mFirstTouchTarget != null) {
                ev.setTargetAccessibilityFocus(false);
            }

            //第三步:檢查cancel
            // Check for cancelation.
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

            //第四步:事件分發(fā)
            // Update list of touch targets for pointer down, if needed.
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;
            if (!canceled && !intercepted) {

                // If the event is targeting accessiiblity focus we give it to the
                // view that has accessibility focus and if it does not handle it
                // we clear the flag and dispatch the event to all children as usual.
                // We are looking up the accessibility focused host to avoid keeping
                // state since these events are very rare.
                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                        ? findChildWithAccessibilityFocus() : null;
                    
                //處理ACTION_DOWN事件,如果子View處理成功,
                //那么mFirstTouchTarget會被賦值
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    final int actionIndex = ev.getActionIndex(); // always 0 for down
                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                            : TouchTarget.ALL_POINTER_IDS;

                    // Clean up earlier touch targets for this pointer id in case they
                    // have become out of sync.
                    removePointersFromTouchTargets(idBitsToAssign);

                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        // 依據(jù)Touch坐標(biāo)尋找子View來接收Touch事件
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        // Find a child that can receive the event.
                        // Scan children from front to back.
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        // 遍歷子View判斷哪個子View接受Touch事件
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);

                            // If there is a view that has accessibility focus we want it
                            // to get the event first and if not handled we will perform a
                            // normal dispatch. We may do a double iteration but this is
                            // safer given the timeframe.
                            if (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }

                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }

                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                                // Child is already receiving touch within its bounds.
                                // Give it the new pointer in addition to the ones it is handling.
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            resetCancelNextUpFlag(child);
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                mLastTouchDownTime = ev.getDownTime();
                                if (preorderedList != null) {
                                    // childIndex points into presorted list, find original index
                                    for (int j = 0; j < childrenCount; j++) {
                                        if (children[childIndex] == mChildren[j]) {
                                            mLastTouchDownIndex = j;
                                            break;
                                        }
                                    }
                                } else {
                                    mLastTouchDownIndex = childIndex;
                                }
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }

                            // The accessibility focus didn't handle the event, so clear
                            // the flag and do a normal dispatch to all children.
                            ev.setTargetAccessibilityFocus(false);
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }

                    if (newTouchTarget == null && mFirstTouchTarget != null) {
                        // Did not find a child to receive the event.
                        // Assign the pointer to the least recently added target.
                        newTouchTarget = mFirstTouchTarget;
                        while (newTouchTarget.next != null) {
                            newTouchTarget = newTouchTarget.next;
                        }
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }

            // Dispatch to touch targets.
            /*mFirstTouchTarget == null有兩種可能,
            一是ACTION_DOWN被攔截或沒有被處理,二是前一個事件被攔截。
            不管是一還是二,當(dāng)前事件和同一序列后續(xù)事件都不會被子View處理*/
            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                // 向子View傳遞一個cancel事件,
                //dispatchTransformedTouchEvent()可以將事件分發(fā)給子View處理
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                //如果之前的事件沒有被攔截
                // Dispatch to touch targets, excluding the new touch target if we already
                // dispatched to it.  Cancel touch targets if necessary.
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        //若當(dāng)前事件被攔截,cancelChild則為true
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        //如果cancelChild為true,那么向子View分發(fā)一個cancel事件
                        //從這里可以看出,如果攔截一個子View的事件,則會向
                        //它分發(fā)一個cancel事件使得它狀態(tài)重置
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        if (cancelChild) {
                            if (predecessor == null) {
                                //如果intercepted為true
                                //mFirstTouchTarget最后會被賦值為null
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }

            //處理ACTION_UP和ACTION_CANCEL,主要是還原狀態(tài)操作
            // Update list of touch targets for pointer up or cancel, if needed.
            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 (!handled && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
        }
        return handled;
    }

然后是View的onTouchEvent:

 public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();
        
        //判斷其屬性是否為DISABLED
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            //如果當(dāng)前事件為ACTION_UP且該View的狀態(tài)為Pressed
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                //清除掉Pressed狀態(tài)
                setPressed(false);
            }
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            //如果這個View是clickable或longClickable,
            //則返回true,即消費(fèi)該事件
            return (((viewFlags & CLICKABLE) == CLICKABLE
                    || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
        }
        //如果設(shè)置有代理,那就執(zhí)行代理的onTouchEvent
        //一般是由于View太小不好按,才會設(shè)置代理。
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
        
        //按照邏輯的連貫性,接下來我們先看ACTION_DOWN,最后再看ACTION_UP
        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    /*檢測到ACTION_UP的時候,
                    不管是Pressed還是PrePressed狀態(tài),只要期間沒有
                    ACTION_MOVE,即Pressed和PrePressed狀態(tài)沒有被取消,
                    就可以執(zhí)行onClick方法,不同的是,由于PrePerssed
                    狀態(tài)還沒有被轉(zhuǎn)換為Pressed狀態(tài)的(mPendingCheckForTap
                    進(jìn)程未被執(zhí)行),所以在這里要setPressed(true, x, y);*/
                    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.
                            // 當(dāng)前并不是Pressed狀態(tài),所以在這里setPressed
                            setPressed(true, x, y);
                       }

                        //如果不是長按事件且下個ACTION_UP事件不被忽略
                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // This is a tap, so remove the longpress check
                            /*移除等待長按的線程,這個線程做的事情其實(shí)就是
                            等待一段時間后調(diào)用longClick方法,如果你按下時間
                            足夠,那就會執(zhí)行這個方法。如果你中途移動或抬起,
                            那這個線程就會被停止*/
                            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方法里面會判斷
                                    //onClickListener是否為null,并執(zhí)行onClick方法
                                    performClick();
                                }
                            }
                        }

                        //下面都是一些還原狀態(tài)的操作
                        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;//初始化長按標(biāo)志為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.
                    /* 如果是在可滑動的容器中,接到ACTION_DOWN事件時,
                    不能直接將View設(shè)置為Pressed狀態(tài),得先等一下
                    (讓手指保持當(dāng)前狀態(tài)115ms,即ViewConfiguration.getTapTimeout()),
                    這是為了避免將外部的滑動當(dāng)作點(diǎn)擊。如果不設(shè)置這個狀態(tài),
                    那么即使用戶想滑動,當(dāng)一碰到就會顯示Pressed的狀態(tài),這是
                    不合理的。在對ACTION_MOVE的處理我們也可以看到,
                    如果滑出了View的范圍,那這個PrePressed
                    狀態(tài)會被去除,如果不是在可滑動的容器中,則直接設(shè)置為
                    Pressed狀態(tài)。*/
                    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;
                //處理取消點(diǎn)擊事件,將狀態(tài)還原
                case MotionEvent.ACTION_CANCEL:
                    setPressed(false);
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    break;

                /*如果手指移出了View的范圍,則取消
                “延遲115ms并設(shè)置為Pressed”
                這一操作,也就是說如果在115ms
                你的手指移動出這一范圍,就不算是Pressed。
                如果已經(jīng)是Pressed狀態(tài),則進(jìn)一步把
                "等待500ms,并設(shè)置為longPressed”
                這一操作也取消了,并setPressed(false)。*/
                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;
    }
    
    
    private final class CheckForTap implements Runnable {
        public void run() {
            /*取消mPrivateFlags的PREPRESSED,
            然后設(shè)置PRESSED標(biāo)識,刷新背景,
            如果View支持長按事件,則再發(fā)一個延時消息,檢測長按;*/
            mPrivateFlags &= ~PREPRESSED;
            mPrivateFlags |= PRESSED;
            refreshDrawableState();
            if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
                postCheckForLongClick(ViewConfiguration.getTapTimeout());
            }
        }
    }

    class CheckForLongPress implements Runnable {

        private int mOriginalWindowAttachCount;

        public void run() {
            // 1、如果此時設(shè)置了長按的回調(diào),則執(zhí)行長按時的回調(diào),且如果長按的回調(diào)返回true;才把mHasPerformedLongPress置為ture;
            // 2、否則,如果沒有設(shè)置長按回調(diào)或者長按回調(diào)返回的是false;則mHasPerformedLongPress依然是false;
            if (isPressed() && (mParent != null)
                && mOriginalWindowAttachCount == mWindowAttachCount) {
                if (performLongClick()) {
                    mHasPerformedLongPress = true;
                }
            }
        }
    }

滑動沖突的處理

外部攔截法

讓在點(diǎn)擊事件都先經(jīng)過父容器的攔截處理,若父容器需要此事件就攔截,若不需要就不攔截。這個方法需要重寫父容器的onInterceptTouchEvent方法,在內(nèi)部做相應(yīng)的攔截即可,
偽代碼如下:

public boolean onInterceptTouchEvent(MotionEvent event){
    boolean intercepted = false;
    switch(event.getAction()){
        case MotionEvent.ACTION_DOWN:
            intercepted = false;
            break;
        case MotionEvent.ACTION_MOVE:
            if(父容器需要當(dāng)前事件){
                intercepted = false;
            }else{
                intercepted = false;
            }
            break;
        case MotionEvent.ACTION_UP:
            intercepted = false;
            break;
        default:
            break;
    }
    return intercepted;
}

這里的ACTION_DOWN必須返回false,否則事件就沒法在傳遞給子元素了,而ACTION_UP在這里意義不大,但考慮到ACTION_UP如果被攔截,那子元素的onClick事件就無法觸發(fā),所以也讓它返回false。

內(nèi)部攔截法

內(nèi)部攔截罰是指父容器不攔截任何事件,所以的事件都傳遞給子元素,如果子元素選喲此事件就直接消耗掉,否則則由父容器來處理,這種方法需要配合requestDisallowInterceptTouchEvent方法才能正常工作。偽代碼如下:

public boolean dispatchTouchEvent(MotionEvent event){
    switch(event.getAction()){
        case MotionEvent.ACTION_DOWN:
            parent.requestDisallowInterceptTouchEvent(true);
            break;
        case MotionEvent.ACTION_MOVE:
            if(父容器需要此類點(diǎn)擊事件){
                parent.requestDisallowIntercepTouchEvent(false);   
            }
            break;
        case MotionEvent.ACTION_UP:
            break;
        case default:
            break;
    }
    return super.dispatchTouchEvent(event);
}

參考資料

《Android開發(fā)藝術(shù)探索》

onepiece2的博客

林子木的博客

大空ts翼的簡書

Rancune的簡書

phantomVK的博客

Quinn的github

Kelin的簡書

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容