Android開發(fā)三《View的事件體系》

一、View的基礎(chǔ)知識

1、View是界面層的控件的一種抽象,包括ViewGroup
2、View的位置參數(shù)
VIew的位置主要由四個頂點來決定,對應(yīng)View的四個屬性:top,left,right,bottom
VIew的坐標系:X軸和Y軸正方向分別為下和右;

注意:以下參數(shù)都是相對于View的父布局坐標
Left = getLeft() 獲取左上角橫坐標
Top = getTop() 獲取左上角縱坐標
Right = getRight() 獲取右下角橫坐標
Bottom = getBottom() 獲取右下角縱坐標
x = getX() 獲取左上角x坐標
y= getY() 獲取左上角y坐標
translationX = getTranslationX() 獲取左上角相對于父容器偏移量
translationY = getTranslationY()  獲取左上角相對于父容器偏移量

注意:這兩個參數(shù)是相對于手機屏幕左上角的坐標
rawX = getRawX() 獲取左上角x坐標
rawY = getRawY() 獲取左上角y坐標

View事件相關(guān)對象

1、MotionEvent:
觸摸事件對象,包括坐標
2等浊、TouchSlop:
系統(tǒng)所能識別的被認為是滑動的最小距離
獲取方式:ViewConfiguration.get(Context).getScaledTouchSlop();
3、VelocityTracker:
速度追蹤,用于追蹤手指在滑動過程中的速度,包括水平和垂直方向速度,由上往下為正值;
速度 =(終點位置- 起點位置)/時間
4摹蘑、GestureDetoctor:
手勢檢測,用于輔助檢測用戶的單擊筹燕、滑動、長按衅鹿、雙擊等行為;一般使用檢測雙擊;
5撒踪、Scroller:
彈性滑動對象,用于實現(xiàn)View的彈性滑動.

二、View的滑動

View滑動方式三種:
1大渤、使用scrollTo/scrollBy,滑動的是內(nèi)容

scroolTo:絕對滑動
mScrollX = view的邊緣- view中內(nèi)容邊緣
所以從左往右滑動,mScrollX為負值,反之為正值;
從上往下滑動,mScroolY為負值,反之為正值;
scroolBy:相對滑動,其內(nèi)部調(diào)用的scrollTo方法

2制妄、平移動畫
translationX,translationY
3、改變布局參數(shù)MarginLayoutParams

三泵三、View的彈性滑動

即滑動漸進式滑動,三種方式
1耕捞、使用Scroller
2、使用動畫
3烫幕、使用延時策略

四俺抽、View的事件分發(fā)機制

事件分發(fā)機制包括三部分:

  1. 從屏幕到App;
  2. 從App到對應(yīng)頁面较曼;
  3. 頁面內(nèi)具體分發(fā)磷斧。

1、事件分發(fā)從屏幕到App

  1. 硬件與內(nèi)核部分
    Linux內(nèi)核會將硬件產(chǎn)生的觸摸事件包裝為Event存到/dev/input/event[x]目錄下捷犹;
  2. SystemServer部分
    在SystemServer進程中管理事件輸入的InputManagerService弛饭,會啟動一個讀線程,也就是InputReader萍歉,它會從系統(tǒng)也就是/dev/input/目錄拿到任務(wù)侣颂,并且分發(fā)給InputDispatcher線程,然后進行統(tǒng)一的事件分發(fā)調(diào)度翠桦。
  3. 跨進程通信傳遞給App
    我們的App中的Window與InputManagerService之間的通信實際上使用的InputChannel(在ViewRootImpl.setView()過程中横蜒,也會同時注冊InputChannel)胳蛮;InputChannel是一個pipe销凑,底層實際是通過socket進行通信。最終會走到InputEventReceiver.dispachInputEvent方法仅炊。

2斗幼、事件分發(fā)從App到對應(yīng)頁面

1. 事件回傳到ViewRootImpl

InputEventReceiver.dispachInputEvent方法即ViewRootImpl中WindowInputEventReceiver實現(xiàn)了InputEventReceiver接口,最終調(diào)用
ViewRootImpl.enqueueInputEvent方法

//InputEventReceiver.java
private void dispatchInputEvent(int seq, InputEvent event) {
    mSeqMap.put(event.getSequenceNumber(), seq);
    onInputEvent(event); 
}

//ViewRootImpl.java ::WindowInputEventReceiver
final class WindowInputEventReceiver extends InputEventReceiver {
    public void onInputEvent(InputEvent event) {
       enqueueInputEvent(event, this, 0, true); 
    }
}

//ViewRootImpl.java
void enqueueInputEvent(InputEvent event,
        InputEventReceiver receiver, int flags, boolean processImmediately) {
    adjustInputEventForCompatibility(event);
    QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);

    QueuedInputEvent last = mPendingInputEventTail;
    if (last == null) {
        mPendingInputEventHead = q;
        mPendingInputEventTail = q;
    } else {
        last.mNext = q;
        mPendingInputEventTail = q;
    }
    mPendingInputEventCount += 1;

    if (processImmediately) {
        //責(zé)任鏈分發(fā)
        doProcessInputEvents(); 
    } else {
        scheduleProcessInputEvents();
    }
}
2. 第一次責(zé)任鏈分發(fā)

調(diào)用doProcessInputEvents()方法抚垄,涉及到事件分發(fā)中的第一次責(zé)任鏈分發(fā)(InputStage的責(zé)任鏈ViewRootImpl.setView方法中就已經(jīng)組裝好了)

不同責(zé)任鏈作用:
SyntheticInputStage蜕窿。綜合處理事件階段谋逻,比如處理導(dǎo)航面板、操作桿等事件桐经。
ViewPostImeInputStage毁兆。視圖輸入處理階段,比如按鍵阴挣、手指觸摸等運動事件气堕,我們熟知的view事件分發(fā)就發(fā)生在這個階段。
NativePostImeInputStage畔咧。本地方法處理階段茎芭,主要構(gòu)建了可延遲的隊列。
EarlyPostImeInputStage誓沸。輸入法早期處理階段梅桩。
ImeInputStage。輸入法事件處理階段拜隧,處理輸入法字符宿百。
ViewPreImeInputStage。視圖預(yù)處理輸入法事件階段洪添,調(diào)用視圖view的dispatchKeyEventPreIme方法犀呼。
NativePreImeInputStage。本地方法預(yù)處理輸入法事件階段薇组。
3. View觸摸事件分發(fā)給ViewPostImeInputStage

最終ViewPostImeInputStage中會調(diào)用mView.dispatchPointerEvent方法外臂;(ViewRootImpl中的mView就是DecorView)

//View.java
    public final boolean dispatchPointerEvent(MotionEvent event) {
            if (event.isTouchEvent()) {
                return dispatchTouchEvent(event);
            } else {
                return dispatchGenericMotionEvent(event);
        }
    }
4. 事件在Activity,Window,DecorView中的傳遞

最終調(diào)用DecorView的dispatchTouchEvent

//DecorView.java
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //cb其實就是對應(yīng)的Activity/Dialog
        final Window.Callback cb = mWindow.getCallback();
        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
    }


//Activity.java
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

//PhoneWindow.java
    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

//DecorView.java
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }    

事件分發(fā)經(jīng)過了: ViewRootImpl -> DecorView -> Activity -> PhoneWindow -> DecorView

3、事件分發(fā)頁面內(nèi)部

1. ViewGroup分發(fā)事件
@Override
    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;

            // Handle an initial down.
            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();
            }

            //檢查是否攔截:只有ActionDown或者mFirstTouchTarget為空時才會判斷是否攔截
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                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);
            }

            // Check for cancelation.
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

            // Update list of touch targets for pointer down, if needed.
            final boolean isMouseEvent = ev.getSource() == InputDevice.SOURCE_MOUSE;
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0
                    && !isMouseEvent;
            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;
            //事件傳遞給子view
            if (!canceled && !intercepted) {
                // If the event is targeting accessibility 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;

                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) {
                        final float x =
                                isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
                        final float y =
                                isMouseEvent ? ev.getYCursorPosition() : 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
                        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;
                            }
                            //2.判斷事件坐標
                            if (!child.canReceivePointerEvents()
                                    || !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);
                            //3.傳遞事件
                            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();
                                //如果子View消耗了則給mFirstTouchTarget賦值
                                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;
                    }
                }
            }

            //mFirstTouchTarget不為空時會調(diào)用dispatchTransformendTouchEvent
            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary 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 {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }

            // 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;
    }
private boolean dispatchTransformedTouchEvent(View child) {
        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else { 
            handled = child.dispatchTouchEvent(event);
        }
    }
  1. 只有當(dāng)Action_Down或者mFirstTouchTarget不為空時才判斷是否攔截律胀;
  2. mFirstTouchTarget是個鏈表結(jié)構(gòu)宋光,代表某個子View消費了事件,為null則表示沒有子View消費事件
  3. disallowIntercept字段是用來是否不攔截炭菌;由子View調(diào)用方法控制罪佳;
getParent().requestDisallowInterceptTouchEvent(true);
  1. mFirstTouchTarget==null有兩種情況,一是ViewGroup攔截黑低,二是子View沒有處理事件,兩種情況最后都回調(diào)到ViewGroup.onTouchEvent赘艳;
  2. dispatchTransformedTouchEvent做的主要就是兩個事,如果child不為null克握,則事件分發(fā)到child,否則調(diào)用super.dispatchTouchEvent,并最終返回結(jié)果
  3. 利用dispatchTransformedTouchEvent,如果返回true,則通過addTouchTarget對mFirstTouchTarget賦值蕾管。
2. 子View分發(fā)事件
public boolean dispatchTouchEvent(MotionEvent event) {
        //如果事件應(yīng)該首先由可訪問性焦點處理。
        if (event.isTargetAccessibilityFocus()) {
            // 我們沒有焦點或沒有虛擬后代有它菩暗,不要處理事件掰曾。
            if (!isAccessibilityFocusedViewOrHost()) {
                return false;
            }
            // 我們有焦點并得到了事件,然后使用正常的事件調(diào)度停团。
            event.setTargetAccessibilityFocus(false);
        }
        boolean result = false;

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

        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // 新手勢的防御性清理
            stopNestedScroll();
        }

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

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

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

        // 如果這是手勢的結(jié)束旷坦,則在嵌套滾動后清理掏熬;如果我們嘗試了 ACTION_DOWN 但我們不想要其余的手勢,也可以取消它秒梅。
        if (actionMasked == MotionEvent.ACTION_UP ||
                actionMasked == MotionEvent.ACTION_CANCEL ||
                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
            stopNestedScroll();
        }

        return result;
    }

子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();

        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

        if ((viewFlags & ENABLED_MASK) == DISABLED
                && (mPrivateFlags4 & PFLAG4_ALLOW_CLICK_WHEN_DISABLED) == 0) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return clickable;
        }
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    if ((viewFlags & TOOLTIP) == TOOLTIP) {
                        handleTooltipUp();
                    }
                    if (!clickable) {
                        removeTapCallback();
                        removeLongPressCallback();
                        mInContextButtonPress = false;
                        mHasPerformedLongPress = false;
                        mIgnoreNextUpEvent = false;
                        break;
                    }
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        // take focus if we don't have it already and we should in
                        // touch mode.
                        boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }

                        if (prepressed) {
                            // The button is being released before we actually
                            // showed it as pressed.  Make it show the pressed
                            // state now (before scheduling the click) to ensure
                            // the user sees it.
                            setPressed(true, x, y);
                        }

                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback();

                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {
                                // Use a Runnable and post this rather than calling
                                // performClick directly. This lets other visual state
                                // of the view update before click actions start.
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClickInternal();
                                }
                            }
                        }

                        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:
                    if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
                        mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
                    }
                    mHasPerformedLongPress = false;

                    if (!clickable) {
                        checkForLongClick(
                                ViewConfiguration.getLongPressTimeout(),
                                x,
                                y,
                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                        break;
                    }

                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }

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

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

                case MotionEvent.ACTION_CANCEL:
                    if (clickable) {
                        setPressed(false);
                    }
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    break;

                case MotionEvent.ACTION_MOVE:
                    if (clickable) {
                        drawableHotspotChanged(x, y);
                    }

                    final int motionClassification = event.getClassification();
                    final boolean ambiguousGesture =
                            motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;
                    int touchSlop = mTouchSlop;
                    if (ambiguousGesture && hasPendingLongPressCallback()) {
                        if (!pointInView(x, y, touchSlop)) {
                            // The default action here is to cancel long press. But instead, we
                            // just extend the timeout here, in case the classification
                            // stays ambiguous.
                            removeLongPressCallback();
                            long delay = (long) (ViewConfiguration.getLongPressTimeout()
                                    * mAmbiguousGestureMultiplier);
                            // Subtract the time already spent
                            delay -= event.getEventTime() - event.getDownTime();
                            checkForLongClick(
                                    delay,
                                    x,
                                    y,
                                    TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                        }
                        touchSlop *= mAmbiguousGestureMultiplier;
                    }

                    // Be lenient about moving outside of buttons
                    if (!pointInView(x, y, touchSlop)) {
                        // Outside button
                        // Remove any future long press/tap checks
                        removeTapCallback();
                        removeLongPressCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            setPressed(false);
                        }
                        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    }

                    final boolean deepPress =
                            motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
                    if (deepPress && hasPendingLongPressCallback()) {
                        // process the long click action immediately
                        removeLongPressCallback();
                        checkForLongClick(0 ,x,y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
                    }
                    break;
            }
            return true;
        }
        return false;
    }
  1. 如果設(shè)置了setOnTouchListener并且返回為true旗芬,那么onTouchEvent就不再執(zhí)行
  2. 否則執(zhí)行onTouchEvent,我們常用的OnClickListenr就是在onTouchEvent里的Up事件中觸發(fā)的捆蜀。

總結(jié):
1岗屏、事件分發(fā)的本質(zhì)就是一個遞歸方法,通過往下傳遞漱办,調(diào)用dispatchTouchEvent方法这刷,找到事件的處理者,這也就是項目中常見的責(zé)任鏈模式娩井。
2暇屋、在分發(fā)過程中,ViewGroup通過onInterceptTouchEvent判斷是否攔截事件
3洞辣、在分發(fā)過程中咐刨,View的默認通過onTouchEvent處理事件
4、如果底層View不消費扬霜,則默認一步步往上執(zhí)行父元素onTouchEvent方法定鸟。
5、如果所有View的onTouchEvent方法都返回false著瓶,則最后會執(zhí)行到Activity的onTouchEvent方法联予,事件分發(fā)也就結(jié)束了。

參考:
Android事件分發(fā)8連問
Android事件分發(fā)

五材原、View的滑動沖突

解決方法:
1沸久、外部攔截法
核心思想:父容器需要的事件攔截,不需要的不攔截;
重寫父容器的onInterceptTouchEvent方法,在內(nèi)部MotionEvent.ACTION_MOVE事件中做相應(yīng)攔截;

/**
 * 攔截事件
 * @param ev
 * @return
 */
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
   boolean intercepted = false;
   int x = (int) ev.getX();
   int y = (int) ev.getY();

   switch (ev.getAction()) {
       /*如果攔截了Down事件,則子類不會拿到這個事件序列*/
       case MotionEvent.ACTION_DOWN:
           lastXIntercept = x;
           lastYIntercept = y;
           intercepted = false;
           break;
       case MotionEvent.ACTION_MOVE:
           final int deltaX = x - lastXIntercept;
           final int deltaY = y - lastYIntercept;
            /*根據(jù)條件判斷是否攔截該事件*/
            if (Math.abs(deltaX) > Math.abs(deltaY)) {
                intercepted = true;
            } else {
                intercepted = false;
            }
            break;
        case MotionEvent.ACTION_UP:
            intercepted = false;
            break;

    }
    lastXIntercept = x;
    lastYIntercept = y;
    return intercepted;
}

2、內(nèi)部攔截法
核心思想:父容器不攔截所有事件,子元素需要此事件就消耗掉,否則交給父容器處理;
重寫子View的dispatchTouchEvent方法,在內(nèi)部MotionEvent.ACTION_DOWN事件中做相應(yīng)攔截;
MotionEvent.ACTION_MOVE事件中根據(jù)條件取消攔截;

ViewGroup中onInterceptTouchEvent方法處理

  @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        //內(nèi)部攔截法
        int action = ev.getAction();
        if (action==MotionEvent.ACTION_DOWN)//down事件不進行攔截
        {
            return false;
        }else {
            return true;
        }
}

子View中dispatchTouchEvent方法處理

  @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        parent= getParent();
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                parent.requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                if (viewgroup需要此事件) //根據(jù)實際情況換成你自己的判定條件
                {
                    parent.requestDisallowInterceptTouchEvent(false);
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末余蟹,一起剝皮案震驚了整個濱河市卷胯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌威酒,老刑警劉巖窑睁,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異葵孤,居然都是意外死亡担钮,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門佛呻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來裳朋,“玉大人,你說我怎么就攤上這事吓著±鸬眨” “怎么了?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵绑莺,是天一觀的道長暖眼。 經(jīng)常有香客問我,道長纺裁,這世上最難降的妖魔是什么诫肠? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮欺缘,結(jié)果婚禮上栋豫,老公的妹妹穿的比我還像新娘。我一直安慰自己谚殊,他們只是感情好丧鸯,可當(dāng)我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嫩絮,像睡著了一般丛肢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上剿干,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天蜂怎,我揣著相機與錄音,去河邊找鬼置尔。 笑死杠步,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的榜轿。 我是一名探鬼主播篮愉,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼差导!你這毒婦竟也來了试躏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤设褐,失蹤者是張志新(化名)和其女友劉穎颠蕴,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體助析,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡犀被,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了外冀。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片寡键。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖雪隧,靈堂內(nèi)的尸體忽然破棺而出西轩,到底是詐尸還是另有隱情员舵,我是刑警寧澤,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布藕畔,位于F島的核電站马僻,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏注服。R本人自食惡果不足惜韭邓,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望溶弟。 院中可真熱鬧女淑,春花似錦、人聲如沸辜御。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽我抠。三九已至苇本,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間菜拓,已是汗流浹背瓣窄。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留纳鼎,地道東北人俺夕。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像贱鄙,于是被迫代替她去往敵國和親劝贸。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,685評論 2 360

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