Android觸摸事件(續(xù))——點(diǎn)擊長(zhǎng)按事件

昨天寫(xiě)完了Android觸摸事件(下)——事件的分發(fā),寫(xiě)完后以為這一部分終將告一段落了。今早無(wú)意間突然想起茂嗓,好像關(guān)于點(diǎn)擊事件、長(zhǎng)按事件這一部分并沒(méi)有分析翱嫡弧!喷橙!垂死病中驚坐起啥么,粗略的看了下源碼,好像沒(méi)啥東西啊贰逾。仔細(xì)看看吧悬荣,發(fā)現(xiàn)有些地方真的是叫人頭疼。沒(méi)辦法疙剑,仔細(xì)看吧看吧氯迂。正是:
碼中自有顏如玉,碼中自有黃金屋言缤。

onTouchEvent會(huì)遲到嚼蚀,有時(shí)也會(huì)缺席

Android觸摸事件(下)——事件的分發(fā)中寫(xiě)過(guò):

  1. 如果設(shè)置了OnTouchListener,那么在執(zhí)行過(guò)程中會(huì)先執(zhí)行OnTouchListener的onTouch方法管挟,接著根據(jù)返回值來(lái)確定是否需要執(zhí)行onTouchEvent方法轿曙。
  2. onTouchEvent是否需要調(diào)用是和result的值有關(guān):如果result為true,則不調(diào)用;反之导帝,則調(diào)用守谓。

所以說(shuō):onTouchEvent會(huì)遲到,有時(shí)也會(huì)缺席您单。不過(guò)缺席的時(shí)候并不是我們關(guān)心的斋荞,我們需要看下正常流程中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();

    // 判斷是否可以點(diǎn)擊
    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
            || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
    // View是否不可用:如果不可用,返回值是是否可點(diǎn)擊
    // 注釋:不可用的但是可點(diǎn)擊的View仍然可以接收觸摸事件睹限,僅僅是不響應(yīng)他們
    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;
        }
    }
    
    // 可點(diǎn)擊或者有標(biāo)志位TOOLTIP
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            // 抬起時(shí)進(jìn)行的操作譬猫,這里面有點(diǎn)擊事件的調(diào)用
            case MotionEvent.ACTION_UP:
                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                if ((viewFlags & TOOLTIP) == TOOLTIP) {
                    handleTooltipUp();
                }
                // 如果不可點(diǎn)擊,則需要把callBack移除
                if (!clickable) {
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    break;
                }
                // 這里Prepressed也用于識(shí)別快速按下
                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.
                    // 如果我們沒(méi)有獲得焦點(diǎn)羡疗,那么我們要根據(jù)觸摸模式來(lái)判斷是否可以獲得焦點(diǎn)
                    // 這里問(wèn)題比較多,后面會(huì)說(shuō)
                    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.
                        // 按下按鈕之前别洪,我們已經(jīng)釋放按鈕叨恨。 現(xiàn)在顯示按下的狀態(tài)(調(diào)度點(diǎn)擊之前),以確保用戶看到它挖垛。
                        setPressed(true, x, y);
                    }

                    // 如果沒(méi)有執(zhí)行長(zhǎng)按事件并且不忽略下次抬起事件
                    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                        // This is a tap, so remove the longpress check
                        // 這是點(diǎn)擊事件痒钝,所以需要移除掉長(zhǎng)按事件的檢查
                        removeLongPressCallback();

                        // Only perform take click actions if we were in the pressed state
                        // 如果當(dāng)前View已經(jīng)獲得焦點(diǎn)或者觸摸模式為false
                        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.
                            // 通過(guò)performClick執(zhí)行click事件
                            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;
                // 如果不可點(diǎn)擊,那么檢查長(zhǎng)按事件
                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();
                // 如果View在正在滾動(dòng)的容器中痢毒,那么延遲發(fā)送這條消息
                // 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
                    // 不在正在滾動(dòng)的容器中送矩,直接設(shè)置按下,并檢查長(zhǎng)按事件
                    setPressed(true, x, y);
                    checkForLongClick(0, x, y);
                }
                break;
    
            // 取消哪替,移除callBack
            case MotionEvent.ACTION_CANCEL:
                if (clickable) {
                    setPressed(false);
                }
                removeTapCallback();
                removeLongPressCallback();
                mInContextButtonPress = false;
                mHasPerformedLongPress = false;
                mIgnoreNextUpEvent = false;
                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                break;
            // 移動(dòng)
            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;
}

上面的代碼去除注釋栋荸,去除移動(dòng)和取消動(dòng)作,真正的代碼并不多:

  1. 判斷View是否不可用:如果不可用凭舶,那么onTouchEvent返回值是否可點(diǎn)擊(clickable )
  2. 如果View可以點(diǎn)擊或者有TOOLTIP標(biāo)志位的話晌块,則進(jìn)行對(duì)事件的不同動(dòng)作的處理。

ACTION_DOWN:主要包括了setPressedcheckForLongClick兩個(gè)操作:

  • setPressed用于設(shè)置按下?tīng)顟B(tài)帅霜,此時(shí)PFLAG_PRESSED標(biāo)志位被設(shè)置匆背。
  • checkForLongClick用于檢查L(zhǎng)ongClick是否可以觸發(fā),以及發(fā)送延遲消息來(lái)響應(yīng)長(zhǎng)按事件身冀。
private void checkForLongClick(int delayOffset, float x, float y) {
    // 如果可以長(zhǎng)按
    if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
        mHasPerformedLongPress = false;

        if (mPendingCheckForLongPress == null) {
            mPendingCheckForLongPress = new CheckForLongPress();
        }
        mPendingCheckForLongPress.setAnchor(x, y);
        mPendingCheckForLongPress.rememberWindowAttachCount();
        // 延遲執(zhí)行CheckForLongPress操作钝尸,時(shí)間默認(rèn)值 DEFAULT_LONG_PRESS_TIMEOUT = 500ms
        postDelayed(mPendingCheckForLongPress,
                ViewConfiguration.getLongPressTimeout() - delayOffset);
    }
}

private final class CheckForLongPress implements Runnable {
    private int mOriginalWindowAttachCount;
    private float mX;
    private float mY;

    @Override
    public void run() {
        if (isPressed() && (mParent != null)
                && mOriginalWindowAttachCount == mWindowAttachCount) {
            // 如果長(zhǎng)按事件返回值true,那么設(shè)置mHasPerformedLongPress為true
            // 表示已經(jīng)執(zhí)行了長(zhǎng)按事件搂根,并且返回值為true
            if (performLongClick(mX, mY)) {
                mHasPerformedLongPress = true;
            }
        }
    }

    public void setAnchor(float x, float y) {
        mX = x;
        mY = y;
    }

    public void rememberWindowAttachCount() {
        mOriginalWindowAttachCount = mWindowAttachCount;
    }
}

// 執(zhí)行長(zhǎng)按事件
public boolean performLongClick(float x, float y) {
    mLongClickX = x;
    mLongClickY = y;
    // 調(diào)用performLongClick()方法
    final boolean handled = performLongClick();
    mLongClickX = Float.NaN;
    mLongClickY = Float.NaN;
    return handled;
}

public boolean performLongClick() {
    // 繼續(xù)調(diào)用
    return performLongClickInternal(mLongClickX, mLongClickY);
}

// 真正執(zhí)行長(zhǎng)按方法 
private boolean performLongClickInternal(float x, float y) {
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);

    boolean handled = false;
    // ListenerInfo中mOnLongClickListener屬性是否不為空珍促,不為空則執(zhí)行onLongClick操作,并將返回值賦給handled
    final ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnLongClickListener != null) {
        handled = li.mOnLongClickListener.onLongClick(View.this);
    }
    if (!handled) {
        final boolean isAnchored = !Float.isNaN(x) && !Float.isNaN(y);
        handled = isAnchored ? showContextMenu(x, y) : showContextMenu();
    }
    if (handled) {
        performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
    }
    return handled;
}

執(zhí)行長(zhǎng)按過(guò)程如下:

  • 判斷是否可以長(zhǎng)按兄墅,可以的話進(jìn)行下面操作踢星。這里如果先設(shè)置不可長(zhǎng)按又設(shè)置OnLongClickListener的話,此時(shí)長(zhǎng)按事件仍有效。但是沐悦,如果順序顛倒下的話成洗,就長(zhǎng)按事件就無(wú)效了。
  • 如果可以長(zhǎng)按藏否,那么通過(guò)HandlerCheckForLongPress延遲發(fā)送瓶殃,時(shí)間是時(shí)間默認(rèn)值 DEFAULT_LONG_PRESS_TIMEOUT = 500ms
  • CheckForLongPress在其run方法中會(huì)根據(jù)performLongClick方法的返回值來(lái)設(shè)置mHasPerformedLongPress變量的值副签,這個(gè)變量的值在后面會(huì)用到遥椿,這里先不說(shuō)。
  • 接著會(huì)一路調(diào)用最終從ListenerInfo中獲得OnLongClickListener淆储,如果不為null冠场,則執(zhí)行其onLongClick方法。

ACTION_UP:

  1. 不可點(diǎn)擊本砰,則需要把callBack移除
  2. 可以點(diǎn)擊的話碴裙,通過(guò)是否可以點(diǎn)擊(clickable)、長(zhǎng)按事件的返回值(mHasPerformedLongPress)点额、是否忽略下次抬起(mIgnoreNextUpEvent)以及焦點(diǎn)是否拿到(focusTaken )這四個(gè)值來(lái)判斷可否執(zhí)行click事件舔株。一般來(lái)說(shuō),大部分博客都會(huì)直接分析performClick過(guò)程还棱,很少會(huì)提到為什么這個(gè)條件會(huì)成立载慈。我這邊深究一下,看下到底為什么能夠執(zhí)行performClick操作:
    2.1. prepressed的值(mPrivateFlags & PFLAG_PREPRESSED) != 0珍手,這個(gè)可以在ACTION_DOWN中可以看到賦值办铡,但賦值的情況是在正在滾動(dòng)的容器中。
    2.2 (mPrivateFlags & PFLAG_PRESSED)此處PFLAG_PRESSED賦值同樣也是在ACTION_DOWN中賦值珠十,與prepressed相反料扰,此時(shí)View不在正在滾動(dòng)的容器中。
    2.3 focusTaken的值焙蹭,這個(gè)值涉及的東西有點(diǎn)多晒杈。首先判斷條件isFocusable() && isFocusableInTouchMode() && !isFocused()isFocusable()一般為trueisFocusableInTouchMode()如果不設(shè)置setFocusableInTouchMode(true)的話孔厉,默認(rèn)為false拯钻;isFocused()這個(gè)值需要注意下,此值意思是是否擁有焦點(diǎn)撰豺,但是我們可以看到判斷條件為!isFocused()粪般,所以如果前面條件都為true的情況下,若此時(shí)isFocused()返回true污桦,那么將不會(huì)再次請(qǐng)求焦點(diǎn)亩歹,因?yàn)榇藭r(shí)已經(jīng)擁有焦點(diǎn),否則,則會(huì)調(diào)用requestFocus獲取焦點(diǎn)小作,并將返回值賦給focusTaken亭姥。
    還是來(lái)看下這邊的代碼吧,挺重要的:
private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {
    // need to be focusable
    if ((mViewFlags & FOCUSABLE) != FOCUSABLE
            || (mViewFlags & VISIBILITY_MASK) != VISIBLE) {
        return false;
    }

    // need to be focusable in touch mode if in touch mode
    if (isInTouchMode() &&
        (FOCUSABLE_IN_TOUCH_MODE != (mViewFlags & FOCUSABLE_IN_TOUCH_MODE))) {
           return false;
    }

    // need to not have any parents blocking us
    if (hasAncestorThatBlocksDescendantFocus()) {
        return false;
    }

    handleFocusGainInternal(direction, previouslyFocusedRect);
    return true;
}

private boolean hasAncestorThatBlocksDescendantFocus() {
    final boolean focusableInTouchMode = isFocusableInTouchMode();
    ViewParent ancestor = mParent;
    // 查找View的父View顾稀,看其是否有FOCUS_BLOCK_DESCENDANTS標(biāo)志位
    // 這里出現(xiàn)一個(gè)常用的變量FOCUS_BLOCK_DESCENDANTS达罗,這里是關(guān)于焦點(diǎn)設(shè)置,后面有代碼分析
    while (ancestor instanceof ViewGroup) {
        final ViewGroup vgAncestor = (ViewGroup) ancestor;
        if (vgAncestor.getDescendantFocusability() == ViewGroup.FOCUS_BLOCK_DESCENDANTS
                || (!focusableInTouchMode && vgAncestor.shouldBlockFocusForTouchscreen())) {
            return true;
        } else {
            ancestor = vgAncestor.getParent();
        }
    }
    return false;
}

void handleFocusGainInternal(@FocusRealDirection int direction, Rect previouslyFocusedRect) {
    // 如果沒(méi)有獲取到焦點(diǎn)静秆,則設(shè)置獲取焦點(diǎn)
    if ((mPrivateFlags & PFLAG_FOCUSED) == 0) {
        mPrivateFlags |= PFLAG_FOCUSED;

        View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null;

        if (mParent != null) {
            mParent.requestChildFocus(this, this);
            updateFocusedInCluster(oldFocus, direction);
        }

        if (mAttachInfo != null) {
            mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
        }

        onFocusChanged(true, direction, previouslyFocusedRect);
        refreshDrawableState();
    }
}

上面的代碼很容易理解粮揉,為什么需要單獨(dú)看一下呢?首先來(lái)說(shuō)抚笔,如果去請(qǐng)求獲取焦點(diǎn)的話扶认,真正獲取成功后此時(shí)返回值為true,那么根據(jù)后面的判斷條件是不會(huì)執(zhí)行performClick操作的殊橙。這里可以假設(shè)有如下代碼:

protected void onCreate(Bundle savedInstanceState) {
        ......
    View view2 = findViewById(R.id.v_2);
    view2.setFocusable(true);
    view2.setFocusableInTouchMode(true);
    view2.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Log.d("fxxk", "view2 onClick");
        }
    });
        ......
}

那么按照上面的分析此時(shí)第一次點(diǎn)擊的時(shí)候應(yīng)該會(huì)去請(qǐng)求焦點(diǎn)的蝠引,此時(shí)點(diǎn)擊事件不會(huì)生效。但蛀柴,真的會(huì)這樣嗎?不會(huì)的矫夯,最初我以為也是這樣鸽疾,但是經(jīng)過(guò)測(cè)試發(fā)現(xiàn):View在設(shè)置成可見(jiàn)(VISIBLE)是,會(huì)調(diào)用mParent.focusableViewAvailable(this);方法训貌。在之前從源碼分析Activity啟動(dòng)時(shí)的生命周期有源碼decor.setVisibility(View.INVISIBLE)制肮,之后會(huì)調(diào)用Activity.makeVisible()mDecor.setVisibility(View.VISIBLE);,這時(shí)候我們看下setVisibility方法:

@RemotableViewMethod
public void setVisibility(@Visibility int visibility) {
    setFlags(visibility, VISIBILITY_MASK);
}

void setFlags(int flags, int mask) {
    final boolean accessibilityEnabled =
            AccessibilityManager.getInstance(mContext).isEnabled();
    final boolean oldIncludeForAccessibility = accessibilityEnabled && includeForAccessibility();

    int old = mViewFlags;
    mViewFlags = (mViewFlags & ~mask) | (flags & mask);

    int changed = mViewFlags ^ old;
    if (changed == 0) {
        return;
    }
    ......

    final int newVisibility = flags & VISIBILITY_MASK;
    // 如果新的狀態(tài)是VISIBLE
    if (newVisibility == VISIBLE) {
        // 如果有改變
        if ((changed & VISIBILITY_MASK) != 0) {
            /*
             * If this view is becoming visible, invalidate it in case it changed while
             * it was not visible. Marking it drawn ensures that the invalidation will
             * go through.
             */
            mPrivateFlags |= PFLAG_DRAWN;
            invalidate(true);

            needGlobalAttributesUpdate(true);

            // a view becoming visible is worth notifying the parent
            // about in case nothing has focus.  even if this specific view
            // isn't focusable, it may contain something that is, so let
            // the root view try to give this focus if nothing else does.
            // 這里的DecorView的Parent是ViewRootImpl
            if ((mParent != null)) {
                // ViewRootImpl的方法
                mParent.focusableViewAvailable(this);
            }
        }
    }

    ......
}

這里偷懶了递沪,把無(wú)關(guān)代碼省去了豺鼻,我們可以看到如果設(shè)置的狀態(tài)和以前不一致的話需要重新根據(jù)狀態(tài)執(zhí)行不同過(guò)程。我們這里設(shè)置的是可見(jiàn)款慨,所以會(huì)執(zhí)行mParent.focusableViewAvailable(this);方法:

ViewRootImpl.java:
@Override
public void focusableViewAvailable(View v) {
    checkThread();
    if (mView != null) {
        // mView是我們的DecorView儒飒,我們并沒(méi)有設(shè)置其獲取焦點(diǎn)
        if (!mView.hasFocus()) {
            if (sAlwaysAssignFocus) {
                // 調(diào)用DecorView的requestFocus方法
                v.requestFocus();
            }
        } else {
            // the one case where will transfer focus away from the current one
            // is if the current view is a view group that prefers to give focus
            // to its children first AND the view is a descendant of it.
            View focused = mView.findFocus();
            if (focused instanceof ViewGroup) {
                ViewGroup group = (ViewGroup) focused;
                if (group.getDescendantFocusability() == ViewGroup.FOCUS_AFTER_DESCENDANTS
                        && isViewDescendantOf(v, focused)) {
                    v.requestFocus();
                }
            }
        }
    }
}
requestFocus會(huì)調(diào)用requestFocus(int direction, Rect previouslyFocusedRect)方法,在ViewGroup中重寫(xiě)檩奠,這里著重看下桩了。
ViewGroup.java:
@Override
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
    int descendantFocusability = getDescendantFocusability();

    switch (descendantFocusability) {
        case FOCUS_BLOCK_DESCENDANTS:
            // 直接去調(diào)用View的requestFocus,不管子View
            return super.requestFocus(direction, previouslyFocusedRect);
        case FOCUS_BEFORE_DESCENDANTS: {
            // 先于子View請(qǐng)求獲取焦點(diǎn)埠戳,如果自身獲取焦點(diǎn)成功井誉,子View不會(huì)請(qǐng)求獲取焦點(diǎn)
            final boolean took = super.requestFocus(direction, previouslyFocusedRect);
            return took ? took : onRequestFocusInDescendants(direction, previouslyFocusedRect);
        }
        case FOCUS_AFTER_DESCENDANTS: {
            // 先讓子View請(qǐng)求獲取焦點(diǎn),如果子View獲取焦點(diǎn)成功整胃,那么父View不會(huì)請(qǐng)求獲取焦點(diǎn)
            final boolean took = onRequestFocusInDescendants(direction, previouslyFocusedRect);
            return took ? took : super.requestFocus(direction, previouslyFocusedRect);
        }
        default:
            throw new IllegalStateException("descendant focusability must be "
                    + "one of FOCUS_BEFORE_DESCENDANTS, FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS "
                    + "but is " + descendantFocusability);
    }
}
ViewGroup.java:
protected boolean onRequestFocusInDescendants(int direction,
        Rect previouslyFocusedRect) {
    int index;
    int increment;
    int end;
    int count = mChildrenCount;
    if ((direction & FOCUS_FORWARD) != 0) {
        index = 0;
        increment = 1;
        end = count;
    } else {
        index = count - 1;
        increment = -1;
        end = -1;
    }
    final View[] children = mChildren;
    for (int i = index; i != end; i += increment) {
        View child = children[i];
        // 只對(duì)可見(jiàn)的View請(qǐng)求獲取焦點(diǎn)颗圣,并且一旦有View獲取焦點(diǎn)則不會(huì)讓其他View請(qǐng)求獲取焦點(diǎn)
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
            if (child.requestFocus(direction, previouslyFocusedRect)) {
                return true;
            }
        }
    }
    return false;
}

ViewGroup獲取焦點(diǎn)時(shí)需要根據(jù)descendantFocusability的值來(lái)判斷,這里descendantFocusability可能出現(xiàn)三個(gè)值:

  1. FOCUS_BLOCK_DESCENDANTS:父View直接請(qǐng)求獲取焦點(diǎn)。
  2. FOCUS_BEFORE_DESCENDANTS:父View會(huì)優(yōu)先其子View在岂,請(qǐng)求獲取焦點(diǎn)奔则,如果沒(méi)有獲取到焦點(diǎn),則會(huì)讓其子View請(qǐng)求獲取焦點(diǎn)洁段。
  3. FOCUS_AFTER_DESCENDANTS:與FOCUS_BEFORE_DESCENDANTS相反应狱,子View會(huì)先請(qǐng)求獲取焦點(diǎn),如果獲取到焦點(diǎn)祠丝,那么父View不會(huì)請(qǐng)求獲取焦點(diǎn)疾呻。

默認(rèn)情況ViewGroup在初始化的時(shí)候設(shè)置為FOCUS_BEFORE_DESCENDANTS,但是DecorView設(shè)置為FOCUS_AFTER_DESCENDANTS


好了写半,到這里我們知道為什么剛才的代碼可以執(zhí)行點(diǎn)擊事件了岸蜗。不過(guò),如果改成下面的代碼執(zhí)行結(jié)果需要自己試試了:

此時(shí)叠蝇,點(diǎn)擊事件執(zhí)行會(huì)出現(xiàn):View1獲取焦點(diǎn)后璃岳,點(diǎn)擊正常,點(diǎn)擊View2悔捶,無(wú)反應(yīng)铃慷,再次點(diǎn)擊事件正常。
我們接著分析繼續(xù)來(lái):
這次我們知道條件成立后會(huì)通過(guò)Handler發(fā)送PerformClick對(duì)象蜕该,如果發(fā)送成功犁柜,則執(zhí)行PerformClick.run方法,否則執(zhí)行performClick()方法(PerformClick.run也調(diào)用了performClick方法)堂淡,最終執(zhí)行OnClickListeneronClick方法馋缅。
performClick

總結(jié)

寫(xiě)了這么多,還是來(lái)個(gè)總結(jié)吧:

  1. 在按下的時(shí)候绢淀,如果長(zhǎng)按事件執(zhí)行了萤悴,并且返回值為false,那么此時(shí)點(diǎn)擊事件不會(huì)執(zhí)行皆的;反之則會(huì)執(zhí)行點(diǎn)擊事件覆履。
  2. 關(guān)于ViewGroup和子View獲取焦點(diǎn)的先后順序,根據(jù)descendantFocusability的值來(lái)判斷:
    FOCUS_BLOCK_DESCENDANTS:父View直接請(qǐng)求獲取焦點(diǎn)祭务。
    FOCUS_BEFORE_DESCENDANTS:父View會(huì)優(yōu)先其子View内狗,請(qǐng)求獲取焦點(diǎn),如果沒(méi)有獲取到焦點(diǎn)义锥,則會(huì)讓其子View請(qǐng)求獲取焦點(diǎn)柳沙。
    FOCUS_AFTER_DESCENDANTS:與FOCUS_BEFORE_DESCENDANTS相反,子View會(huì)先請(qǐng)求獲取焦點(diǎn)拌倍,如果獲取到焦點(diǎn)赂鲤,那么父View不會(huì)請(qǐng)求獲取焦點(diǎn)噪径。
  3. 如果同一個(gè)頁(yè)面中,有多個(gè)View都可以獲得焦點(diǎn)数初,那么只有當(dāng)前獲取焦點(diǎn)的點(diǎn)擊事件可以正常執(zhí)行找爱,其他View需要先點(diǎn)擊一次獲取焦點(diǎn),之后可以正常執(zhí)行點(diǎn)擊事件泡孩。

OK车摄,這篇分析到此結(jié)束了。如果有問(wèn)題仑鸥,請(qǐng)指出(估計(jì)也就是自己來(lái)發(fā)現(xiàn)問(wèn)題吧)吮播。
這里多說(shuō)幾句:之前,我覺(jué)得許多博客分析的真的是頭頭是道眼俊,當(dāng)輪到我去分析某些東西的時(shí)候再去看他們的博客卻發(fā)現(xiàn):好多東西都說(shuō)的不明確意狠,太模糊。所以疮胖,自己分析的時(shí)候盡量把所有過(guò)程全部分析完成环戈,爭(zhēng)取步驟完整,方便以后自己回顧澎灸。
That's all!


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末院塞,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子性昭,更是在濱河造成了極大的恐慌迫悠,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件巩梢,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡艺玲,警方通過(guò)查閱死者的電腦和手機(jī)括蝠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)饭聚,“玉大人忌警,你說(shuō)我怎么就攤上這事∶胧幔” “怎么了法绵?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)酪碘。 經(jīng)常有香客問(wèn)我朋譬,道長(zhǎng),這世上最難降的妖魔是什么兴垦? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任徙赢,我火速辦了婚禮字柠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘狡赐。我一直安慰自己窑业,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布枕屉。 她就那樣靜靜地躺著常柄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪搀擂。 梳的紋絲不亂的頭發(fā)上西潘,一...
    開(kāi)封第一講書(shū)人閱讀 49,760評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音哥倔,去河邊找鬼秸架。 笑死,一個(gè)胖子當(dāng)著我的面吹牛咆蒿,可吹牛的內(nèi)容都是我干的东抹。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼沃测,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼缭黔!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起蒂破,我...
    開(kāi)封第一講書(shū)人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤馏谨,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后附迷,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體惧互,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年喇伯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了喊儡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡稻据,死狀恐怖艾猜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情捻悯,我是刑警寧澤匆赃,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站今缚,受9級(jí)特大地震影響算柳,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜姓言,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一埠居、第九天 我趴在偏房一處隱蔽的房頂上張望查牌。 院中可真熱鬧,春花似錦滥壕、人聲如沸纸颜。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)胁孙。三九已至,卻和暖如春称鳞,著一層夾襖步出監(jiān)牢的瞬間涮较,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工冈止, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留狂票,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓熙暴,卻偏偏與公主長(zhǎng)得像闺属,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子周霉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

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