如何成為自定義高手(四)觸摸反饋,事件分發(fā)機(jī)制

觸摸反饋,事件分發(fā)機(jī)制

觸摸反饋是事件分發(fā)機(jī)制是永遠(yuǎn)都繞不開的話題,也是一切的基礎(chǔ)和理論。網(wǎng)上也有講的很好的,大家多多少少也有自己的理解。我這邊也就寫一些的我理解,和一些別人總結(jié)比較好的東西。

總結(jié)(如果你能全部想通下面所有的總結(jié),說明對(duì)事件分發(fā)比較了解,總結(jié)之后的內(nèi)容就不需要看)
  • 同一個(gè)事件序列是指從手指接觸屏幕的那一刻起,到手指離開屏幕的那一刻結(jié)束,在這個(gè)過程中所產(chǎn)生的一系列事件,這個(gè)事件序列從down事件開始,中間含有數(shù)量不定的move事件,最終以u(píng)p事件結(jié)束
  • 正常情況下,一個(gè)事件序列只能被一個(gè)View攔截且消耗。因?yàn)橐坏┮粋€(gè)元素?cái)r截了某此事件,那么同一個(gè)事件序列內(nèi)的所有事件都會(huì)直接交個(gè)它處理,因此同一個(gè)事件序列中的事件不能分別由兩個(gè) View同時(shí)處理,但是通過特殊手段可以做到(利用傳遞序列中上一個(gè)ViewGroup的onInterceptTouchEvent),比如一個(gè)View將本該自己處理的事件通過onTouchEvent強(qiáng)行傳遞給其他View處理。
  • 某個(gè)View一旦決定攔截,那么這一個(gè)事件序列都只能由它來處理(如果事件序列能傳遞到它的話),并且它的onInterceptTouchEvent不會(huì)再被調(diào)用。這條也很好理解,就是說當(dāng)一個(gè)View決定攔截一個(gè)事件后,那么系統(tǒng)會(huì)把同一個(gè)事件序列內(nèi)的其他方法都直接交給它來處理,因此就不用再調(diào)用這個(gè)View的onInterceptTouchEvent去詢問他是否要攔截。
  • 某個(gè)View一旦開始處理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回false),那么同一事件序列中的其他事件都不會(huì)再交給它處理。并且事件將重新交由它的父元素去處理,即父元素的onTouchEvent會(huì)被調(diào)用。意思是事件一旦交給一個(gè)View處理,它必須消耗掉,否則同一事件序列中剩下的事件就不再交個(gè)它來處理了。
  • ViewGroup默認(rèn)不攔截任何事件。Android源碼中ViewGroup的onInterceptTouchEvent方法就會(huì)默認(rèn)返回false。
  • View沒有onInterceptTouchEvent方法,一旦有點(diǎn)擊事件傳遞給它,那么它的onTouchEvent方法就會(huì)被調(diào)用。
  • View的enable屬性不影響onTouchEvent的默認(rèn)返回值。哪怕一個(gè)View是diable狀態(tài),只要它的clickable或者longClickable有一個(gè)為true,那么它的onTouchEvent就會(huì)返回true。
  • onClick會(huì)發(fā)生的前提是當(dāng)前View是可點(diǎn)擊的,并且它收到了down和up的事件。
  • 事件傳遞過程由外向內(nèi)的,即事件總是先傳遞給父元素,然后再由父元素分發(fā)給子View,通過requestDisallowInterceptTouchEvent方法可以在子元素中干預(yù)父元素的事件分發(fā)過程,但是ACTION_DOWN事件除外。

上面的很多結(jié)論會(huì)在下面的分發(fā)流程,源碼解析中出現(xiàn)。

當(dāng)手指按下ACTION_DOWN,會(huì)進(jìn)行事件分發(fā)的流程

下面么有3張圖,從簡(jiǎn)單到詳細(xì)的表描述事件的分發(fā)流程。
其實(shí)我們常常說事件分發(fā)有三個(gè)事件,其實(shí)dispatchTouchEvent事件中包含onInterceptTouchEvent和onTouchEvent。當(dāng)然view中是沒有onInterceptTouchEvent事件的。


只針對(duì)ACTION_DOWN事件.png

可以通過代碼來概括這個(gè)流程:

public boolean dispatchTouchEvent(){
      boolean result = false;
      if(onInterceptTouchEvent()){
            result = onTouchEvent();
      }else{
            result = child.dispatchTouchEvent();
      }
      return result;
}

圖二先對(duì)第一幅更加詳細(xì),整個(gè)流程從Activity的dispatchTouchEvent開始,總體東西是差不多的。


圖二.png

圖三是圖二中各個(gè)流程中用到源碼方法的詳情的介紹。


image.png
ACTION_DOWN后事件系列何去何從

ACTION_DOWN事件在哪個(gè)控件消費(fèi)了(return true), 那么ACTION_MOVE和ACTION_UP就會(huì)從上往下(通過dispatchTouchEvent)做事件分發(fā)往下傳,就只會(huì)傳到這個(gè)控件,不會(huì)繼續(xù)往下傳,如果ACTION_DOWN事件是在dispatchTouchEvent消費(fèi),那么事件到此為止停止傳遞,如果ACTION_DOWN事件是在onTouchEvent消費(fèi)的,那么會(huì)把ACTION_MOVE或ACTION_UP事件傳給該控件的onTouchEvent處理并結(jié)束傳遞。

紅色的箭頭代表ACTION_DOWN 事件的流向
藍(lán)色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向


圖1.png

圖2.png

某個(gè)View一旦決定攔截,那么這一個(gè)事件序列都只能由它來處理(如果事件序列能傳遞到它的話),并且它的onInterceptTouchEvent不會(huì)再被調(diào)用。這條也很好理解,就是說當(dāng)一個(gè)View決定攔截一個(gè)事件后,那么系統(tǒng)會(huì)把同一個(gè)事件序列內(nèi)的其他方法都直接交給它來處理,因此就不用再調(diào)用這個(gè)View的onInterceptTouchEvent去詢問他是否要攔截。
圖3.png

View.dispatchTouchEvent

dispatchTouchEvent中這段代代碼,可以知道會(huì)先去檢查onTouch()的返回值,如果為false,再去給機(jī)會(huì)調(diào)用onTouchEvent(),所以onTouch方法的優(yōu)先級(jí)大于onTouchEvent()。

public boolean dispatchTouchEvent(MotionEvent event) {      
        ......
        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            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;
            }
        }
        ......
        return result;
    }

View.onTouchEvent

首先介紹一個(gè)在下面會(huì)用的TouchTarget(記錄每個(gè)子 View 是被哪些 pointer(?指)按下的,是單向鏈表)

第一步:判斷view是否clickable,無論是CLICKABLE,LONG_CLICKABL,ECONTEXT_CLICKABLE都是clickable
第二步:如果view是clickable的,及時(shí)是disable,只是不做出響應(yīng),但是依然會(huì)消耗事件。
第三步:如果view是clickable以及設(shè)置tooltip,則進(jìn)入下面判斷點(diǎn)擊,長(zhǎng)按的監(jiān)聽檢測(cè)以及處理。下面的分析中我就不分析tooltip的情況,源碼中有很多代碼是因?yàn)榧嫒輙ooltip添加。

  • ACTION_DOWN: isInScrollingContainer()方法判斷是否在滑動(dòng)組件中,如果在滑動(dòng)組件isInScrollingContainer置為true。如果在滑動(dòng)組件中,延遲設(shè)置按下的反饋100毫秒,檢查是否要滑動(dòng);如果不在滑動(dòng)組件中,立即顯示按下的反饋并檢查長(zhǎng)按。
  • ACTION_MOVE:這里相對(duì)簡(jiǎn)單就是如果手指移到外面就會(huì)取消掉tap和長(zhǎng)按的回調(diào)等一系列后續(xù)處理,pointInView(x, y, mTouchSlop),mTouchSlop會(huì)有一個(gè)手指觸摸到外面的余量
  • ACTION_UP: if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed)如果之前是按下或者預(yù)按下則進(jìn)入下面的判斷。首先獲取焦點(diǎn),然后如果之前是是預(yù)按下的設(shè)置按下setPressed(true, x, y),包括在抬起前做延遲給一個(gè)按下的效果。如果符合if (!mHasPerformedLongPress && !mIgnoreNextUpEvent)的判斷中,說明是一個(gè)點(diǎn)擊事件執(zhí)行PerformClick。
/**
     * Implement this method to handle touch screen motion events.
     * <p>
     * If this method is used to detect click actions, it is recommended that
     * the actions be performed by implementing and calling
     * {@link #performClick()}. This will ensure consistent system behavior,
     * including:
     * <ul>
     * <li>obeying click sound preferences
     * <li>dispatching OnClickListener calls
     * <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
     * accessibility features are enabled
     * </ul>
     *
     * @param event The motion event.
     * @return True if the event was handled, false otherwise.
     */
    public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();
        //第一步  context_clickable內(nèi)容可點(diǎn)擊
        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
        //第二步
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            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)) {
                                    performClick();
                                }
                            }
                        }

                        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(0, x, y);
                        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(0, x, y);
                    }
                    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);
                    }

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

            return true;
        }

        return false;
    }

ViewGroup.dispatchTouchEvent

整體流程結(jié)構(gòu)如下

  • 如果是?戶初次按下(ACTION_DOWN),清空 TouchTargets 和 DISALLOW_INTERCEPT 標(biāo)記
  • 攔截處理
  • 如果不攔截并且不是 CANCEL 事件,并且是 DOWN 或者 POINTER_DOWN,嘗試把
    pointer(?指)通過 TouchTarget 分配給子View;并且如果分配給了新的子 View,調(diào)?
    child.dispatchTouchEvent() 把事件傳給子View
  • 看有沒有 TouchTarget
    如果沒有,調(diào)用自己的 super.dispatchTouchEvent()
    如果有,調(diào)用child.dispatchTouchEvent() 把事件傳給對(duì)應(yīng)的子 View(如果有的話)
  • 如果是 POINTER_UP,從 TouchTargets 中清除 POINTER 信息;如果是 UP 或 CANCEL,重置
    狀態(tài)
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.
        // 輔助功能 事件將會(huì)第一個(gè)派發(fā)給開啟了accessibility focused的view
        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
            ev.setTargetAccessibilityFocus(false);
        }
 
        boolean handled = false;
        // 表示窗口是否為模糊窗口(FILTER_TOUCHES_WHEN_OBSCURED),如果是窗口,則表示不希望處理該事件。(如dialog后的窗口)
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            // 過濾字段的最后8bit,也就是指只關(guān)心是ACTION_DOWN、ACTION_UP等事件,而不關(guān)心是哪個(gè)手指引起的。
            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.
                // 初始化相關(guān)狀態(tài)
                //(01) 清空mFirstTouchTarget鏈表,并設(shè)置mFirstTouchTarget為null。mFirstTouchTarget是"接受觸摸事件的View"所組成的單鏈表
                //(02) 清空mGroupFlags的FLAG_DISALLOW_INTERCEPT標(biāo)記,如果設(shè)置了FLAG_DISALLOW_INTERCEPT,ViewGroup對(duì)觸摸事件進(jìn)行攔截。
                //(03) 清空mPrivateFlags的PFLAG_CANCEL_NEXT_UP_EVEN標(biāo)記,作用是將下一個(gè)時(shí)間變?yōu)镃ancel
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }
 
            // Check for interception.
            final boolean intercepted;
            // 如果為DOWN事件,或者mFirstTouchTarget為null(那么事件直接給到自己),就沒必要執(zhí)行攔截。
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                // 查看是否設(shè)置了,禁止攔截的標(biāo)記
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    // ViewGroup的onInterceptTouchEvent不執(zhí)行攔截,除非子類重寫了該方法(如listview)
                    intercepted = onInterceptTouchEvent(ev);
                    // 僅僅是避免action被篡改過。
                    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.
            // 查看時(shí)候被標(biāo)記了PFLAG_CANCEL_NEXT_UP_EVENT 或者 當(dāng)前是一個(gè)Cancel事件
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;
 
            // Update list of touch targets for pointer down, if needed.
            // 比如我們多個(gè)手指放到了屏幕上,是否要將第二個(gè)手指的事件下面下去
            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.
               
               dWithAccessibilityFocus = 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.
                    // 清除Targets中相應(yīng)的pointer ids 
                    removePointersFromTouchTargets(idBitsToAssign);
 
                    // 遍歷所有的child,將事件派發(fā)下去
                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        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.
                        // 找到一個(gè)可以接受事件的子view,從上面往下面找
                        // 以 1)Z軸(5.0系統(tǒng)引入) 2)draw的順序 進(jìn)行排序
                        final ArrayList<View> preorderedList = buildOrderedChildList();
 
                        // 可以理解為,是否按照draw的順序(因?yàn)?,buildOrderedChildList在都沒有設(shè)置Z的情況下返回null)
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            // 這里兩端代碼,簡(jiǎn)單的理解根據(jù)不同的排列選項(xiàng)(1、view添加到 2、view的draw順序 3、viewZ 軸順序)
                            final int childIndex = customOrder
                                    ? getChildDrawingOrder(childrenCount, i) : i;
                            final View child = (preorderedList == null)
                                    ? children[childIndex] : preorderedList.get(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.
                            // 如果存在開啟了AccessibilityFocus 的view
                            if (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                // 如果正是當(dāng)前的childView開啟了AccessibilityFocus,直接將i指向最后一個(gè)元素
                                // 和 break的區(qū)別是,還將執(zhí)行后面的代碼,但是不會(huì)再進(jìn)行循環(huán)了
                                i = childrenCount - 1;
                            }
                            // canViewReceivePointerEvents 判斷child是否為visiable 或者 是否有動(dòng)畫
                            // isTransformedTouchPointInView 判斷x, y是否在view的區(qū)域內(nèi)(如果是執(zhí)行了補(bǔ)間動(dòng)畫 則x,y會(huì)通過獲取的matrix變換值
                            // 換算當(dāng)相應(yīng)的區(qū)域,這也是為什么補(bǔ)間動(dòng)畫的觸發(fā)區(qū)域不隨著動(dòng)畫而改變)
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }
                            // getTouchTarget 查找child是否已經(jīng)記錄在mFirstTouchTarget這個(gè)單鏈表中
                            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);
                            // 簡(jiǎn)單的理解,dispatchTransformedTouchEvent就是將相應(yīng)的事件傳遞下去
                            // 不過需要注意一點(diǎn)的就是,event被傳遞給child的時(shí)候?qū)?huì)做相應(yīng)偏移,如下
                            // final float offsetX = mScrollX - child.mLeft;
                            // final float offsetY = mScrollY - child.mTop;
                            // event.offsetLocation(offsetX, offsetY);
                            // 為什么要做偏移呢? 因?yàn)閑vent的getX得到的值是,childView到parentView邊境的距離,是一個(gè)相對(duì)值
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // dispatchTransformedTouchEvent交個(gè)childView去做dispatchTouchEvent
                                //if (child == null) {
                                //  handled = super.dispatchTouchEvent(event);
                                //} else {
                                //  handled = child.dispatchTouchEvent(event);
                                //}
                                // Child wants to receive touch within its bounds.
                                mLastTouchDownTime = ev.getDownTime();
                                if (preorderedList != null) {
                                    // childIndex points into presorted list, find original index
                                    // 找到childIndex所代表的child的最原始的index【?】看代碼,children和mChildren指向同一鏈表
                                    for (int j = 0; j < childrenCount; j++) {
                                        if (children[childIndex] == mChildren[j]) {
                                            mLastTouchDownIndex = j;
                                            break;
                                        }
                                    }
                                } else {
                                    mLastTouchDownIndex = childIndex;
                                }
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                // 將相應(yīng)該事件的child包裝成一個(gè)Target,添加到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();
                    }
 
                    // 如果沒有child響應(yīng)該事件,則將此事件交給最近加入的target
                    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 == null 表示,沒有能響應(yīng)該事件的child,那么就調(diào)用父類(也就是View)的dispatchTouchEvent
            // Dispatch to touch targets.
            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;
                    // 表示在Down事件處理中,已經(jīng)將這個(gè)事件交給newTouchTarget處理過了,就不重復(fù)處理了
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        // 再次判定是否需要cancel(被標(biāo)記為cancel 或者 事件被攔截)
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        // 問:難道看到這里你們會(huì)不會(huì)產(chǎn)生一個(gè)疑問,如果parent在ACTION_MOVE過程中攔截了該事件,哪里處理呢?
                        // 答:如果攔截了該事件,還是需要自身 dispatchTransformedTouchEvent 函數(shù)將事件交個(gè)自己的onTouchEvent
                        //     此外dispatchTransformedTouchEvent完成上述操作需要一個(gè)條件,也就是child形參數(shù)為null
                        // 問:那么怎么為null呢?
                        // 答:往上面看10幾行,不就是了嗎?
                        // 那么如何達(dá)到,其實(shí)條件就是 mFirstTouchTarget == null, 請(qǐng)看下面的分析
                        //     如果intercepted == true的情況下, cancelChild == true, predecessor == null
                        // 從而使得mFirstTouchTarget 一直 -> next,當(dāng)target遍歷到最后的時(shí)候,next == null,從而使得mFirstTouchTarget == null。
                        // 問: 稍等,這里僅僅做了將mFirstTouchTarget 設(shè)置了為null,那么如何派發(fā)給自己的onTouchEvent呢?
                        // 這個(gè)只能等下一個(gè)事件過來了
                        // 結(jié)論【事件攔截,攔截了該事件,并沒有將本次這個(gè)事件傳遞給自身的onTouchEvent,而需要等到下次】
                        // 問:如何驗(yàn)證
                        // 答:重新FrameLayout的 onInterceptTouchEvent 和 onTouchEvent 將相應(yīng)的event.getEventTime打印出來,
                        //     將會(huì)發(fā)現(xiàn)攔截的事件和傳遞到onTouchEvent的時(shí)間不是一個(gè)時(shí)間。
                        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.
            // cancel ACTION_UP  ACTION_HOVER_MOVE(表示鼠標(biāo)滑動(dòng))等,清理狀態(tài)
            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);
            }
        }
 
        // 調(diào)試使用,可以忽略 
        if (!handled && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
        }
        return handled;
    }
}

如何成為自定義高手(一)繪制
如何成為自定義高手(二)動(dòng)畫
如何成為自定義高手(三)布局
如何成為自定義高手(四)觸摸反饋,事件分發(fā)機(jī)制
如何成為自定義高手(五)多點(diǎn)觸摸
如何成為自定義高手(六)滑動(dòng)和拖拽
如何成為自定義高手(七)滑動(dòng)沖突

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

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

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