Android TV開(kāi)發(fā)焦點(diǎn)移動(dòng)源碼分析

點(diǎn)可以理解為選中態(tài)盐数,在Android TV上起很重要的作用。一個(gè)視圖控件只有在獲得焦點(diǎn)的狀態(tài)下傻粘,才能響應(yīng)按鍵的Click事件沼瘫。
相對(duì)于手機(jī)上用手指點(diǎn)擊屏幕產(chǎn)生的Click事件抬纸, 在TV中通過(guò)點(diǎn)擊遙控器的方向鍵來(lái)控制焦點(diǎn)的移動(dòng)。當(dāng)焦點(diǎn)移動(dòng)到目標(biāo)控件上之后耿戚,按下遙控器的確定鍵湿故,才會(huì)觸發(fā)一個(gè)Click事件,進(jìn)而去做下一步的處理
在處理焦點(diǎn)的時(shí)候膜蛔,有一些基礎(chǔ)的用法需要知道坛猪。
首先,一個(gè)控件isFocusable()需要為true才有資格可以獲取到焦點(diǎn)皂股。如果想要在觸摸模式下獲取焦點(diǎn)墅茉,需要通過(guò)setFocusableInTouchMode(boolean)來(lái)設(shè)置。也可以直接在xml布局文件中指定:

android:focusable="true"呜呐,
 android:focusableInTouchMode="true"

一就斤、KeyEvent分發(fā)

keyEvent 分發(fā)過(guò)程:


image.png

而當(dāng)按下遙控器的按鍵時(shí),會(huì)產(chǎn)生一個(gè)按鍵事件蘑辑,就是KeyEvent洋机,包含“上”,“下”洋魂,“左”绷旗,“右”,“返回”副砍,“確定”等指令衔肢。焦點(diǎn)的處理就在KeyEvent的分發(fā)當(dāng)中完成。
首先豁翎,KeyEvent會(huì)流轉(zhuǎn)到ViewRootImpl中開(kāi)始進(jìn)行處理角骤,具體方法是內(nèi)部類(lèi)ViewPostImeInputStage中的processKeyEvent

private int processKeyEvent(QueuedInputEvent q) {
    // 將系統(tǒng)輸入轉(zhuǎn)為keyevent事件
    final KeyEvent event = (KeyEvent)q.mEvent;

    // 1. 先由DecorView進(jìn)行按鍵事件派發(fā)
    if (mView.dispatchKeyEvent(event)) {
        return FINISH_HANDLED;
    }

    ......

    // Handle automatic focus changes.
    if (event.getAction() == KeyEvent.ACTION_DOWN) {
        if (groupNavigationDirection != 0) {
            if (performKeyboardGroupNavigation(groupNavigationDirection)) {
                return FINISH_HANDLED;
            }
        } else {
            //2. 處理鍵盤(pán)的上下左右的焦點(diǎn)查找
            if (performFocusNavigation(event)) {
                return FINISH_HANDLED;
            }
        }
    }
    return FORWARD;
}
  1. mView.dispatchKeyEvent 由DecorView進(jìn)行按鍵事件派發(fā)。返回 true事件消耗谨垃,不往下執(zhí)行焦點(diǎn)搜索與請(qǐng)求启搂,返回 false,繼續(xù)往下執(zhí)行刘陶。
  2. 如果事件沒(méi)有被view框架消耗,之后會(huì)通過(guò)focusSearch去找下一個(gè)焦點(diǎn)view

接下來(lái)先看一下KeyEvent在view框架中的分發(fā):

  1. DecorView 的 dispatchKeyEvent 函數(shù)
public boolean dispatchKeyEvent(KeyEvent event) {
    final int keyCode = event.getKeyCode();
    final int action = event.getAction();
    final boolean isDown = action == KeyEvent.ACTION_DOWN;
    
    ......

    if (!mWindow.isDestroyed()) {
        // 這里的cb就是activity對(duì)象牢撼,Activity實(shí)現(xiàn)了Window.Callback接口
        final Window.Callback cb = mWindow.getCallback();
        // cb.dispatchKeyEvent 調(diào)用的是 Activity的dispatchKeyEvent
        final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
            : super.dispatchKeyEvent(event);
        // 是否消耗事件
        if (handled) {
            return true;
        }
    }

    return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)
        : mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);
}
  1. Activity 的 dispatchKeyEvent 函數(shù)
public boolean dispatchKeyEvent(KeyEvent event) {
    onUserInteraction();

    final int keyCode = event.getKeyCode();
    if (keyCode == KeyEvent.KEYCODE_MENU &&
        mActionBar != null && mActionBar.onMenuKeyEvent(event)) {
        return true;
    }

    Window win = getWindow();
    // 調(diào)用 PhoneWindow 的 superDispatchKeyEvent
    // 里面又調(diào)用 mDecor.superDispatchKeyEvent(event)
    // mDecor為 DecorView.
    if (win.superDispatchKeyEvent(event)) {
        return true;
    }
    View decor = mDecor;
    if (decor == null) decor = win.getDecorView();
    return event.dispatch(this, decor != null
                          ? decor.getKeyDispatcherState() : null, this);
}

這里也是可以做焦點(diǎn)控制匙隔,最好是在 event.getAction() == KeyEvent.ACTION_DOWN 進(jìn)行.
因?yàn)閍ndroid 的 ViewRootlmpl 的 processKeyEvent 焦點(diǎn)搜索與請(qǐng)求的地方 進(jìn)行了判斷if (event.getAction() == KeyEvent.ACTION_DOWN)

  1. DecorView 的 superDispatchKeyEvent 函數(shù)
public boolean superDispatchKeyEvent(KeyEvent event) {
    // Give priority to closing action modes if applicable.
    if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
        final int action = event.getAction();
        // Back cancels action modes first.
        if (mPrimaryActionMode != null) {
            if (action == KeyEvent.ACTION_UP) {
                mPrimaryActionMode.finish();
            }
            return true;
        }
    }
    //DecorView繼承FrameLayout 這里調(diào)用的是 ViewGroup.dispatchKeyEvent
    if (super.dispatchKeyEvent(event)) {
        return true;
    }
    return (getViewRootImpl() != null) && getViewRootImpl().dispatchUnhandledKeyEvent(event);
}
  1. ViewGroup 的 dispatchKeyEvent 函數(shù)
public boolean dispatchKeyEvent(KeyEvent event) {
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onKeyEvent(event, 1);
    }

    if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
        == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
        // 調(diào)用 view.dispatchKeyEvent
        if (super.dispatchKeyEvent(event)) {
            return true;
        }
    } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
               == PFLAG_HAS_BOUNDS) {
        // 調(diào)用 focus view 的 dispatchKeyEvent
        if (mFocused.dispatchKeyEvent(event)) {
            return true;
        }
    }

    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
    }
    return false;
}

? 首先ViewGroup會(huì)一層一層往上執(zhí)行父類(lèi)的dispatchKeyEvent方法,如果返回true那么父類(lèi)的dispatchKeyEvent方法就會(huì)返回true熏版,也就代表父類(lèi)消費(fèi)了該焦點(diǎn)事件纷责,那么焦點(diǎn)事件自然就不會(huì)往下進(jìn)行分發(fā)捍掺。
? 然后ViewGroup會(huì)判斷mFocused這個(gè)view是否為空,如果為空就會(huì)return false再膳,焦點(diǎn)繼續(xù)往下傳遞挺勿;如果不為空,那就會(huì)return mFocused的dispatchKeyEvent方法返回的結(jié)果喂柒。這個(gè)mFocused其實(shí)是ViewGroup中當(dāng)前獲取焦點(diǎn)的子View

  1. 最后調(diào)用 View 的 dispatchKeyEvent
public boolean dispatchKeyEvent(KeyEvent event) {
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onKeyEvent(event, 0);
    }

    // 調(diào)用 mOnKeyListener onKey回調(diào)不瓶,如果這里也沒(méi)有消耗事件,繼續(xù)往下面執(zhí)行
    ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
        && li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
        return true;
    }

    // 主要是處理一些回調(diào)灾杰,比如 onKeyDown蚊丐,onKeyLongPress,onKeyUp等等
    if (event.dispatch(this, mAttachInfo != null
                       ? mAttachInfo.mKeyDispatchState : null, this)) {
        return true;
    }

    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }
    return false;
}

發(fā)現(xiàn)執(zhí)行了onKeyListener中的onKey方法艳吠,如果onKey方法返回true麦备,那么dispatchKeyEvent方法也會(huì)返回true
如果想要修改ViewGroup焦點(diǎn)事件的分發(fā)
? 重寫(xiě)view的dispatchKeyEvent方法
? 給某個(gè)子view設(shè)置onKeyListener監(jiān)聽(tīng)

二、第一次獲取焦點(diǎn)

下面再來(lái)看一下如果一個(gè)頁(yè)面第一次進(jìn)入昭娩,系統(tǒng)是如何確定焦點(diǎn)是定位在哪個(gè)view上的


image.png
  1. ViewRootImpl中 performTraversals方法發(fā)起焦點(diǎn)獲取
if (mFirst) {
    if (sAlwaysAssignFocus || !isInTouchMode()) {
        // handle first focus request
        if (DEBUG_INPUT_RESIZE) {
            Log.v(mTag, "First: mView.hasFocus()=" + mView.hasFocus());
        }
        if (mView != null) {
            if (!mView.hasFocus()) {
                // 調(diào)用 View 的 restoreDefaultFocus
                mView.restoreDefaultFocus();
                if (DEBUG_INPUT_RESIZE) {
                    Log.v(mTag, "First: requested focused view=" + mView.findFocus());
                }
            } else {
                if (DEBUG_INPUT_RESIZE) {
                    Log.v(mTag, "First: existing focused view=" + mView.findFocus());
                }
            }
        }
    } else {
        // Some views (like ScrollView) won't hand focus to descendants that aren't within
        // their viewport. Before layout, there's a good change these views are size 0
        // which means no children can get focus. After layout, this view now has size, but
        // is not guaranteed to hand-off focus to a focusable child (specifically, the edge-
        // case where the child has a size prior to layout and thus won't trigger
        // focusableViewAvailable).
        View focused = mView.findFocus();
        if (focused instanceof ViewGroup
            && ((ViewGroup) focused).getDescendantFocusability()
            == ViewGroup.FOCUS_AFTER_DESCENDANTS) {
            focused.restoreDefaultFocus();
        }
    }
}
  1. View.restoreDefaultFocus
public boolean restoreDefaultFocus() {
    return requestFocus(View.FOCUS_DOWN);
}

由于DecorView繼承自FrameLayout凛篙,這里調(diào)用的是ViewGroup的requestFocus

  1. ViewGroup.requestFocus
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
   
    int descendantFocusability = getDescendantFocusability();

    boolean result;
    switch (descendantFocusability) {
        case FOCUS_BLOCK_DESCENDANTS:
            result = super.requestFocus(direction, previouslyFocusedRect);
            break;
        case FOCUS_BEFORE_DESCENDANTS: {
            final boolean took = super.requestFocus(direction, previouslyFocusedRect);
            result = took ? took : onRequestFocusInDescendants(direction,
                                                               previouslyFocusedRect);
            break;
        }
        case FOCUS_AFTER_DESCENDANTS: {
            // 調(diào)用 onRequestFocusInDescendants 遍歷子控件進(jìn)行請(qǐng)求
            final boolean took = onRequestFocusInDescendants(direction, previouslyFocusedRect);
            result = took ? took : super.requestFocus(direction, previouslyFocusedRect);
            break;
        }
        default:
            throw new IllegalStateException("descendant focusability must be "
                                            + "one of FOCUS_BEFORE_DESCENDANTS, FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS "
                                            + "but is " + descendantFocusability);
    }
    if (result && !isLayoutValid() && ((mPrivateFlags & PFLAG_WANTS_FOCUS) == 0)) {
        mPrivateFlags |= PFLAG_WANTS_FOCUS;
    }
    return result;
}

descendantFocusability:
? FOCUS_AFTER_DESCENDANTS:先分發(fā)給Child View進(jìn)行處理,如果所有的Child View都沒(méi)有處理栏渺,則自己再處理
? FOCUS_BEFORE_DESCENDANTS:ViewGroup先對(duì)焦點(diǎn)進(jìn)行處理鞋诗,如果沒(méi)有處理則分發(fā)給child View進(jìn)行處理
? FOCUS_BLOCK_DESCENDANTS:ViewGroup本身進(jìn)行處理,不管是否處理成功迈嘹,都不會(huì)分發(fā)給ChildView進(jìn)行處理
因?yàn)?PhoneWindow 給 DecoreView 初始化時(shí)設(shè)置 了 setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS)削彬,所以這里默認(rèn)是FOCUS_AFTER_DESCENDANTS

  1. ViewGroup.onRequestFocusInDescendants 遍歷子控件
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];
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
            // 遍歷子view調(diào)用requestFocus
            if (child.requestFocus(direction, previouslyFocusedRect)) {
                return true;
            }
        }
    }
    return false;
}
  1. 子view爭(zhēng)取焦點(diǎn)
# View.java
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
    return requestFocusNoSearch(direction, previouslyFocusedRect);
}

private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {
    // 如果focusable為false直接return
    if (!canTakeFocus()) {
        return false;
    }

    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;
    }

    if (!isLayoutValid()) {
        mPrivateFlags |= PFLAG_WANTS_FOCUS;
    } else {
        clearParentsWantFocus();
    }
    // 關(guān)鍵函數(shù)
    handleFocusGainInternal(direction, previouslyFocusedRect);
    return true;
}

void handleFocusGainInternal(@FocusRealDirection int direction, Rect previouslyFocusedRect) {

    if ((mPrivateFlags & PFLAG_FOCUSED) == 0) {
        mPrivateFlags |= PFLAG_FOCUSED;
        // 獲取父布局的老焦點(diǎn).
        View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null;

        if (mParent != null) {
            // 調(diào)用requestChildFocus,告訴上一層父布局秀仲,
            mParent.requestChildFocus(this, this);
            updateFocusedInCluster(oldFocus, direction);
        }

        if (mAttachInfo != null) {
            //全局焦點(diǎn)監(jiān)聽(tīng)的回調(diào).
            // 調(diào)用方式: View.getViewTreeObserver().addOnGlobalFocusChangeListener
            mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
        }
        // 回調(diào)處理.
        onFocusChanged(true, direction, previouslyFocusedRect);
        refreshDrawableState();
    }
}
# ViewGroup.java
public void requestChildFocus(View child, View focused) {
    if (DBG) {
        System.out.println(this + " requestChildFocus()");
    }
    if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
        return;
    }

    // Unfocus us, if necessary
    super.unFocus(focused);

    // We had a previous notion of who had focus. Clear it.
    if (mFocused != child) {
        if (mFocused != null) {
            mFocused.unFocus(focused);
        }
        // 保存焦點(diǎn)
        mFocused = child;
    }
    if (mParent != null) {
        // 一層一層的回調(diào)父布局
        mParent.requestChildFocus(this, focused);
    }
}

到此第一次請(qǐng)求焦點(diǎn)的過(guò)程基本告一個(gè)段落

三融痛、焦點(diǎn)搜索

焦點(diǎn)移動(dòng)的時(shí)候,默認(rèn)的情況下神僵,會(huì)按照一種算法去找在指定移動(dòng)方向上最近的鄰居雁刷。在一些情況下,焦點(diǎn)的移動(dòng)可能跟開(kāi)發(fā)者的意圖不符保礼,這時(shí)開(kāi)發(fā)者可以在布局文件中使用下面這些XML屬性來(lái)指定下一個(gè)焦點(diǎn)對(duì)象:

nextFocusDown
nextFocusLeft
nextFocusRight
nextFocusUp

在KeyEvent分發(fā)中已經(jīng)知道如果分發(fā)過(guò)程中event沒(méi)有被消耗沛励,就會(huì)根據(jù)方向搜索以及請(qǐng)求焦點(diǎn)View


image.png
  1. performFocusNavigation
    dispatchKeyEvent過(guò)程中沒(méi)有view消耗keyEvent,如果event.getAction() == KeyEvent.ACTION_DOWN 則調(diào)用performFocusNavigation搜索下一個(gè)焦點(diǎn)
private boolean performFocusNavigation(KeyEvent event) {
    int direction = 0;

    ......

    if (direction != 0) {
        // 一層一層的查找炮障,找到真正的焦點(diǎn)view
        View focused = mView.findFocus();
        if (focused != null) {
            // 調(diào)用焦點(diǎn)view的focusSearch進(jìn)行焦點(diǎn)搜索
            View v = focused.focusSearch(direction);
            if (v != null && v != focused) {
                // do the math the get the interesting rect
                // of previous focused into the coord system of
                // newly focused view
                focused.getFocusedRect(mTempRect);
                if (mView instanceof ViewGroup) {
                    ((ViewGroup) mView).offsetDescendantRectToMyCoords(
                        focused, mTempRect);
                    ((ViewGroup) mView).offsetRectIntoDescendantCoords(
                        v, mTempRect);
                }
                // 調(diào)用搜索到的view的requestFocus進(jìn)行焦點(diǎn)獲取目派,流程同第一次焦點(diǎn)獲取
                if (v.requestFocus(direction, mTempRect)) {
                    playSoundEffect(SoundEffectConstants
                                    .getContantForFocusDirection(direction));
                    return true;
                }
            }

            // 進(jìn)行最后的垂死掙扎,
            // 這里可以處理一些焦點(diǎn)問(wèn)題或者滾動(dòng)翻頁(yè)問(wèn)題
            if (mView.dispatchUnhandledMove(focused, direction)) {
                return true;
            }
        } else {
            if (mView.restoreDefaultFocus()) {
                return true;
            }
        }
    }
    return false;
}
  1. 調(diào)用View的focusSearch開(kāi)始搜索焦點(diǎn)
    View并不會(huì)直接去找焦點(diǎn)胁赢,而是交給它的parent去找企蹭。逐漸調(diào)用VIewGroup的focusSearch方法去搜索知道最外層的布局。最終實(shí)際上調(diào)用的是FocusFinder.getInstance().findNextFocus
# View
public View focusSearch(@FocusRealDirection int direction) {
    if (mParent != null) {
        return mParent.focusSearch(this, direction);
    } else {
        return null;
    }
}
# ViewGroup
public View focusSearch(View focused, int direction) {
    if (isRootNamespace()) {
        // root namespace means we should consider ourselves the top of the
        // tree for focus searching; otherwise we could be focus searching
        // into other tabs.  see LocalActivityManager and TabHost for more info.
        return FocusFinder.getInstance().findNextFocus(this, focused, direction);
    } else if (mParent != null) {
        return mParent.focusSearch(focused, direction);
    }
    return null;
}
  1. FocusFinder的indNextFocus方法
private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
    View next = null;
    ViewGroup effectiveRoot = getEffectiveRoot(root, focused);
    // 流程一:
    if (focused != null) {
        // 尋找用戶指定的下一個(gè)焦點(diǎn)
        next = findNextUserSpecifiedFocus(effectiveRoot, focused, direction);
    }
    //如果找到,直接返回用戶指定的焦點(diǎn)
    if (next != null) {
        return next;
    }
    // 流程二:
    ArrayList<View> focusables = mTempList;
    try {
        focusables.clear();
        // 把當(dāng)前root下的所有direction方向上可以獲得焦點(diǎn)的view加入列表
        effectiveRoot.addFocusables(focusables, direction);
        if (!focusables.isEmpty()) {
            // 繼續(xù)尋找當(dāng)前root下的焦點(diǎn)
            next = findNextFocus(effectiveRoot, focused, focusedRect, direction, focusables);
        }
    } finally {
        focusables.clear();
    }
    return next;
}

流程一:查找用戶指定的下一個(gè)焦點(diǎn)

1. FocusFinder findUserSetNextFocus()找到用戶指定的下一個(gè)焦點(diǎn)
private View findNextUserSpecifiedFocus(ViewGroup root, View focused, int direction) {
    // check for user specified next focus
    View userSetNextFocus = focused.findUserSetNextFocus(root, direction);
    View cycleCheck = userSetNextFocus;
    boolean cycleStep = true; // we want the first toggle to yield false
    while (userSetNextFocus != null) {
        // 判斷是否可以獲得焦點(diǎn)
        if (userSetNextFocus.isFocusable()
            && userSetNextFocus.getVisibility() == View.VISIBLE
            && (!userSetNextFocus.isInTouchMode()
                || userSetNextFocus.isFocusableInTouchMode())) {
            return userSetNextFocus;
        }
        userSetNextFocus = userSetNextFocus.findUserSetNextFocus(root, direction);
        if (cycleStep = !cycleStep) {
            cycleCheck = cycleCheck.findUserSetNextFocus(root, direction);
            if (cycleCheck == userSetNextFocus) {
                // found a cycle, user-specified focus forms a loop and none of the views
                // are currently focusable.
                break;
            }
        }
    }
    return null;
}

2. View.findUserSetNextFocus
View findUserSetNextFocus(View root, @FocusDirection int direction) {
    switch (direction) {
        case FOCUS_LEFT:
            // 如果mNextFocusLeftId == View.NO_ID 即用戶沒(méi)有指定下一個(gè)焦點(diǎn)直接返回null
            if (mNextFocusLeftId == View.NO_ID) return null;
            return findViewInsideOutShouldExist(root, mNextFocusLeftId);
        case FOCUS_RIGHT:
            ......
        }
    }
    return null;
}
3. View.findViewInsideOutShouldExist
private View findViewInsideOutShouldExist(View root, int id) {
    if (mMatchIdPredicate == null) {
        mMatchIdPredicate = new MatchIdPredicate();
    }
    // 要尋找的下一個(gè)焦點(diǎn)的view的id
    mMatchIdPredicate.mId = id;
    View result = root.findViewByPredicateInsideOut(this, mMatchIdPredicate);
    if (result == null) {
        Log.w(VIEW_LOG_TAG, "couldn't find view with id " + id);
    }
    return result;
}

4. View.findViewByPredicateInsideOut
public final <T extends View> T findViewByPredicateInsideOut(
        View start, Predicate<View> predicate) {
    View childToSkip = null;
    for (;;) {
        // 判斷一下start跟id指定的view是否是同一個(gè)谅摄,同一個(gè)直接返回
        T view = start.findViewByPredicateTraversal(predicate, childToSkip);
        if (view != null || start == this) {
            return view;
        }
        // 如果沒(méi)有找到徒河,則一層層找到start的父view繼續(xù)比較
        ViewParent parent = start.getParent();
        if (parent == null || !(parent instanceof View)) {
            return null;
        }

        childToSkip = start;
        start = (View) parent;
    }
}

5. View.findViewByPredicateTraversal 查找子view中是否有對(duì)應(yīng)id的view
protected <T extends View> T findViewByPredicateTraversal(Predicate<View> predicate,View childToSkip) {
    if (predicate.test(this)) {
        return (T) this;
    }

    final View[] where = mChildren;
    final int len = mChildrenCount;

    for (int i = 0; i < len; i++) {
        View v = where[i];

        if (v != childToSkip && (v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
            v = v.findViewByPredicate(predicate);

            if (v != null) {
                return (T) v;
            }
        }
    }

    return null;
}

流程二:獲取搜索方向上所有可以獲取焦點(diǎn)的view,使用算法查找下一個(gè)view
addFocusables() 獲取搜索方向上可獲得焦點(diǎn)的view

public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
    final int focusableCount = views.size();

    final int descendantFocusability = getDescendantFocusability();
    final boolean blockFocusForTouchscreen = shouldBlockFocusForTouchscreen();
    final boolean focusSelf = (isFocusableInTouchMode() || !blockFocusForTouchscreen);

    // 覆蓋子view送漠,自己獲取焦點(diǎn)
    if (descendantFocusability == FOCUS_BLOCK_DESCENDANTS) {
        if (focusSelf) {
            super.addFocusables(views, direction, focusableMode);
        }
        return;
    }

    if (blockFocusForTouchscreen) {
        focusableMode |= FOCUSABLES_TOUCH_MODE;
    }
    // 自己優(yōu)先獲取焦點(diǎn)
    if ((descendantFocusability == FOCUS_BEFORE_DESCENDANTS) && focusSelf) {
        super.addFocusables(views, direction, focusableMode);
    }

    int count = 0;
    final View[] children = new View[mChildrenCount];
    for (int i = 0; i < mChildrenCount; ++i) {
        View child = mChildren[i];
        // 判斷view是否可見(jiàn)
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
            children[count++] = child;
        }
    }
    // 根據(jù)位置對(duì)children進(jìn)行排序
    FocusFinder.sort(children, 0, count, this, isLayoutRtl());
    for (int i = 0; i < count; ++i) {
        children[i].addFocusables(views, direction, focusableMode);
    }

    // When set to FOCUS_AFTER_DESCENDANTS, we only add ourselves if
    // there aren't any focusable descendants.  this is
    // to avoid the focus search finding layouts when a more precise search
    // among the focusable children would be more interesting.
    if ((descendantFocusability == FOCUS_AFTER_DESCENDANTS) && focusSelf
        && focusableCount == views.size()) {
        super.addFocusables(views, direction, focusableMode);
    }
}

descendantFocusability屬性決定了ViewGroup和其子view的聚焦優(yōu)先級(jí)
? FOCUS_BLOCK_DESCENDANTS:viewgroup會(huì)覆蓋子類(lèi)控件而直接獲得焦點(diǎn)
? FOCUS_BEFORE_DESCENDANTS:viewgroup會(huì)覆蓋子類(lèi)控件而直接獲得焦點(diǎn)
? FOCUS_AFTER_DESCENDANTS:viewgroup只有當(dāng)其子類(lèi)控件不需要獲取焦點(diǎn)時(shí)才獲取焦點(diǎn)
addFocusables的第一個(gè)參數(shù)views是由root決定的顽照。在ViewGroup的focusSearch方法中傳進(jìn)來(lái)的root是DecorView,也可以主動(dòng)調(diào)用FocusFinder的findNextFocus方法闽寡,在指定的ViewGroup中查找焦點(diǎn)代兵。
FocusFinder.findNextFocus 查找焦點(diǎn)

private View findNextFocus(ViewGroup root, View focused, Rect focusedRect,
                           int direction, ArrayList<View> focusables) {
    // 1. 焦點(diǎn)不為空的情況
    if (focused != null) {
        if (focusedRect == null) {
            focusedRect = mFocusedRect;
        }
        // fill in interesting rect from focused
        focused.getFocusedRect(focusedRect);
        root.offsetDescendantRectToMyCoords(focused, focusedRect);
    } else {
        // 2. 焦點(diǎn)為空的情況
        if (focusedRect == null) {
            focusedRect = mFocusedRect;
            // make up a rect at top left or bottom right of root
            switch (direction) {
                case View.FOCUS_RIGHT:
                case View.FOCUS_DOWN:
                    setFocusTopLeft(root, focusedRect);
                    break;
                case View.FOCUS_FORWARD:
                    if (root.isLayoutRtl()) {
                        setFocusBottomRight(root, focusedRect);
                    } else {
                        setFocusTopLeft(root, focusedRect);
                    }
                    break;

                case View.FOCUS_LEFT:
                case View.FOCUS_UP:
                    setFocusBottomRight(root, focusedRect);
                    break;
                case View.FOCUS_BACKWARD:
                    if (root.isLayoutRtl()) {
                        setFocusTopLeft(root, focusedRect);
                    } else {
                        setFocusBottomRight(root, focusedRect);
                        break;
                    }
            }
        }
    }

    switch (direction) {
        case View.FOCUS_FORWARD:
        case View.FOCUS_BACKWARD:
            return findNextFocusInRelativeDirection(focusables, root, focused, focusedRect,
                                                    direction);
        case View.FOCUS_UP:
        case View.FOCUS_DOWN:
        case View.FOCUS_LEFT:
        case View.FOCUS_RIGHT:
            return findNextFocusInAbsoluteDirection(focusables, root, focused,
                                                    focusedRect, direction);
        default:
            throw new IllegalArgumentException("Unknown direction: " + direction);
    }
}

View findNextFocusInAbsoluteDirection(ArrayList<View> focusables, ViewGroup root, View focused,
                                      Rect focusedRect, int direction) {
    // initialize the best candidate to something impossible
    // (so the first plausible view will become the best choice)
    mBestCandidateRect.set(focusedRect);
    switch(direction) {
        case View.FOCUS_LEFT:
            // mBestCandidateRect在focusedReact的右邊,并且距離focusedReact的右邊一個(gè)像素
            mBestCandidateRect.offset(focusedRect.width() + 1, 0);
            break;
        case View.FOCUS_RIGHT:
            mBestCandidateRect.offset(-(focusedRect.width() + 1), 0);
            break;
        case View.FOCUS_UP:
            mBestCandidateRect.offset(0, focusedRect.height() + 1);
            break;
        case View.FOCUS_DOWN:
            mBestCandidateRect.offset(0, -(focusedRect.height() + 1));
    }

    View closest = null;

     //遍歷可獲得焦點(diǎn)的列表
    int numFocusables = focusables.size();
    for (int i = 0; i < numFocusables; i++) {
        View focusable = focusables.get(i);

        // only interested in other non-root views
        if (focusable == focused || focusable == root) continue;

        // get focus bounds of other view in same coordinate system
        focusable.getFocusedRect(mOtherRect);
        root.offsetDescendantRectToMyCoords(focusable, mOtherRect);

        //找到最佳的候選的view,則返回
        if (isBetterCandidate(direction, focusedRect, mOtherRect, mBestCandidateRect)) {
            mBestCandidateRect.set(mOtherRect);
            closest = focusable;
        }
    }
    return closest;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末下隧,一起剝皮案震驚了整個(gè)濱河市奢人,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌淆院,老刑警劉巖何乎,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異土辩,居然都是意外死亡支救,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)拷淘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)各墨,“玉大人,你說(shuō)我怎么就攤上這事启涯”岫拢” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵结洼,是天一觀的道長(zhǎng)黎做。 經(jīng)常有香客問(wèn)我,道長(zhǎng)松忍,這世上最難降的妖魔是什么蒸殿? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮鸣峭,結(jié)果婚禮上宏所,老公的妹妹穿的比我還像新娘。我一直安慰自己摊溶,他們只是感情好爬骤,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著更扁,像睡著了一般盖腕。 火紅的嫁衣襯著肌膚如雪赫冬。 梳的紋絲不亂的頭發(fā)上浓镜,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天溃列,我揣著相機(jī)與錄音,去河邊找鬼膛薛。 笑死听隐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的哄啄。 我是一名探鬼主播雅任,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼咨跌!你這毒婦竟也來(lái)了沪么?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤锌半,失蹤者是張志新(化名)和其女友劉穎禽车,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體刊殉,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡殉摔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了记焊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逸月。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖遍膜,靈堂內(nèi)的尸體忽然破棺而出碗硬,到底是詐尸還是另有隱情,我是刑警寧澤瓢颅,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布恩尾,位于F島的核電站,受9級(jí)特大地震影響惜索,放射性物質(zhì)發(fā)生泄漏特笋。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一巾兆、第九天 我趴在偏房一處隱蔽的房頂上張望猎物。 院中可真熱鬧,春花似錦角塑、人聲如沸蔫磨。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)堤如。三九已至蒲列,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間搀罢,已是汗流浹背蝗岖。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留榔至,地道東北人抵赢。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像唧取,于是被迫代替她去往敵國(guó)和親铅鲤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

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