Android焦点流程梳理

作者:Cy13er

前言

最近在看一些焦点处理的问题,认真处理起来发现不跟着源码自己走一遍焦点相关的流程,对于问题的分析上会比较困难。所以本文主要对 焦点流程 进行一次梳理,在处理类似问题时也可以作为手册阅读。

起源

一切都要从 ViewRootImpl 收到输入事件 开始

// ViewRootImpl.ViewPostImeInputStage.java
@Override
protected int onProcess(QueuedInputEvent q) {
    if (q.mEvent instanceof KeyEvent) {
        return processKeyEvent(q); // KeyEvent会在此处处理
    } else {
        final int source = q.mEvent.getSource();
        if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
            return processPointerEvent(q);
        } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
            return processTrackballEvent(q);
        } else {
            return processGenericMotionEvent(q);
private int processKeyEvent(QueuedInputEvent q) {
    final KeyEvent event = (KeyEvent)q.mEvent;
    // 1\. KeyEvent事件分发
    if (mView.dispatchKeyEvent(event)) {
        return FINISH_HANDLED;
    if (event.getAction() == KeyEvent.ACTION_DOWN) {
        if (groupNavigationDirection != 0) {
            if (performKeyboardGroupNavigation(groupNavigationDirection)) {
                return FINISH_HANDLED;
        } else {
            // 2\. ACTION_DOWN时,寻找下一个获取焦点的View
            if (performFocusNavigation(event)) {
                return FINISH_HANDLED;
    return FORWARD;
}

焦点流程主要分成两部分问题:

  • KeyEvent事件分发, 寻找当前焦点需要消费事件的子View,并消费事件 ,见注释1处
  • 若事件 没有消费 ,而且当前是 KeyEvent.ACTION_DOWN 时,需要 寻找下一个焦点 ,见注释2处

寻找下一个焦点

// ViewRootImpl.ViewPostImeInputStage.java
private boolean performFocusNavigation(KeyEvent event) {
    int direction = 0;
    switch (event.getKeyCode()) {
        case KeyEvent.KEYCODE_DPAD_LEFT:
            if (event.hasNoModifiers()) {
                direction = View.FOCUS_LEFT;
            break;
        case KeyEvent.KEYCODE_DPAD_RIGHT:
            if (event.hasNoModifiers()) {
                direction = View.FOCUS_RIGHT;
            break;
        case KeyEvent.KEYCODE_DPAD_UP:
            if (event.hasNoModifiers()) {
                direction = View.FOCUS_UP;
            break;
        case KeyEvent.KEYCODE_DPAD_DOWN:
            if (event.hasNoModifiers()) {
                direction = View.FOCUS_DOWN;
            break;
        case KeyEvent.KEYCODE_TAB:
            if (event.hasNoModifiers()) {
                direction = View.FOCUS_FORWARD;
            } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
                direction = View.FOCUS_BACKWARD;
            break;
    if (direction != 0) {
        // 1\. 获取目前拥有焦点的View
        View focused = mView.findFocus();
        if (focused != null) {
            // 2\. 查找到下一个获取焦点的View
            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);
                // 3\. 让View获取焦点
                if (v.requestFocus(direction, mTempRect)) {
                    boolean isFastScrolling = event.getRepeatCount() > 0;
                    playSoundEffect(
                            SoundEffectConstants.getConstantForFocusDirection(direction,
                                    isFastScrolling));
                    return true;
            // Give the focused view a last chance to handle the dpad key.
            if (mView.dispatchUnhandledMove(focused, direction)) {
                return true;
        } else {
            if (mView.restoreDefaultFocus()) {
                return true;
    return false;
}

大概就是3步:

  1. 通过 findFocus 获取到 当前拥有焦点 的View
  2. 通过 focusSearch 获取到 下一个获取焦点 的View
  3. 让下一个获取焦点的View 获取焦点

大概流程

这里大概流程如下

按键事件分发

dispatchKeyEvent

KeyEvent事件分发 与触摸事件有类似 ,都是从顶层的DecorView开始寻找消费事件的子View。不同的是,它没有过多复杂的机制,譬如事件拦截。

ViewGroup#dispatchKeyEvent

// ViewGroup.java
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
    // 1\. 如果父View自己拥有焦点,则判断自己是否消费事件
    if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
            == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
        if (super.dispatchKeyEvent(event)) {
            return true;
    // 2\. 如果该父View包含拥有焦点的子View,将事件分发到对应子View方向    
    } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
            == PFLAG_HAS_BOUNDS) {
        if (mFocused.dispatchKeyEvent(event)) {
            return true;
    return false;
}

ViewGroup#dispatchKeyEvent 分两种情况:

  • 注释1,如果父View 自己拥有焦点 ,则判断自己是否消费事件,实际是调用了父类View中定义的 dispatchKeyEvent 判断。
  • 注释2,如果父View当前 包含了拥有焦点的子View ,则将事件 往拥有焦点的子View方向传递

也就是说,KeyEvent事件分发 只会往拥有焦点的子View方向传递 。也就有以下两种情况:

ps:红色部分是事件传递的方向。

View#dispatchKeyEvent

// View.java
public boolean dispatchKeyEvent(KeyEvent event) {
    // Give any attached key listener a first crack at the event.
    //noinspection SimplifiableIfStatement
    ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
            && li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
        return true;
    if (event.dispatch(this, mAttachInfo != null
            ? mAttachInfo.mKeyDispatchState : null, this)) {
        return true;
    return false;
}

View#dispatchKeyEvent 就与dispatchTouchEvent类似了,第一步

  • 判断 OnKeyListener 是否为 null
  • 判断 OnKeyListener.onKey 是否返回 true
  • View是否是 Enable 状态

三者成立则认为View消费事件 ,否则通过 View#onKeyDown或者View#onKeyUp 判断。 事件的调用通过 KeyEvent#dispatch ,也就是注释2。

ps:与Touch事件类似, DecorView 会先将事件传递给Activity,然后经过 PhoneWindow 回传给 DecorView 进行事件分发

获取当前拥有焦点的View

findFocus

findFocus 的作用就是 获取到当前拥有焦点的View ,View的实现就是通过判断 mPrivateFlags 来确定自己是否拥有焦点。如果自己拥有焦点则返回自己。

// View.java
public View findFocus() {
    return (mPrivateFlags & PFLAG_FOCUSED) != 0 ? this : null;
}

ViewGroup的实现会在View的基础上扩展:

  • 如果自己拥有焦点则返回自己。
  • 如果自己 包含了拥有焦点的子View ,则往拥有焦点的子View方向寻找。
// ViewGroup.java
@Override
public View findFocus() {
    if (DBG) {
        System.out.println("Find focus in " + this + ": flags="
                + isFocused() + ", child=" + mFocused);
    if (isFocused()) {
        return this;
    if (mFocused != null) {
        return mFocused.findFocus();
    return null;
}

也就是下图中,红色箭头表示 findFocus 的调用方向,红色区域表示 当前拥有焦点的View

下一个获取焦点的View

focusSearch

focusSearch 寻找 下一个获取焦点的View ,它是以当前拥有焦点的View作为起点, 往上传递 。View的实现中,会 调用父View的 focusSearch ,将自己也就是拥有焦点的View传递上去,同时携带当前需要移动的方向 direction

// View.java
public View focusSearch(@FocusRealDirection int direction) {
    if (mParent != null) {
        return mParent.focusSearch(this, direction);
    } else {
        return null;
}

ViewGroup中,会分为两种情况:

  • 没有到达 根视图,那么与View相同直接传递上去。
  • 如果 到达 根视图,则会通过 FocusFinder 这一单例来寻找下一个获取焦点的View。ps:这里的根视图可以理解成是View树的最上层。
// ViewGroup.java
@Override
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;
}

ps: FocusFinder.getInstance().findNextFocus(this, focused, direction) 中的 this 的意思是以当前View作为起点寻找下一个获取焦点的View。

方法的调用方向如下图红色部分:

焦点搜索控制技巧

可能会有这样一个需求:使用自定义RecyclerView 做一个Tabbar效果 时, 不想在移动到最左或者最右时,焦点移出RecyclerView ,那么可以从 focusSearch 入手,拦截它的搜索过程

override fun focusSearch(focused: View?, direction: Int): View? {
    if (focused == null || layoutManager == null || adapter == null || adapter?.itemCount == 0)
        return super.focusSearch(focused, direction)
    val view = super.focusSearch(focused, direction)
    if (view != null) {
        //  findContainingItemView获取到该view所在的父View,如果不在RecyclerView内则返回null
        val nextFocusItemView = findContainingItemView(view)
        //  为null证明焦点已经移出了RecyclerView
        if (nextFocusItemView == null) {    
            //  左右限制移出RecyclerView
            if (direction == View.FOCUS_LEFT || direction == View.FOCUS_RIGHT) {
                return focused
    return view
}

这里如果是左右移动到边界,则直接返回当前拥有焦点的View,使焦点还在当前View上,不会被移动到别处。

FocusFinder

// FocusFinder.java
private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
    View next = null;
    ViewGroup effectiveRoot = getEffectiveRoot(root, focused);
    if (focused != null) {
        // 1\. 获取开发者指定的下一个获取焦点的View
        next = findNextUserSpecifiedFocus(effectiveRoot, focused, direction);
    if (next != null) {
        return next;
    ArrayList<View> focusables = mTempList;
    try {
        focusables.clear();
        // 2\. 通过遍历子View,可以获取焦点的备选View放入focusables集合中
        effectiveRoot.addFocusables(focusables, direction);
        if (!focusables.isEmpty()) {
            // 3\. 通过可以获取焦点的备选view中获取到下一个获取焦点的View
            next = findNextFocus(effectiveRoot, focused, focusedRect, direction, focusables);
    } finally {
        focusables.clear();
    return next;
}

FocusFinder.getInstance().findNextFocus 中主要做了以下3件事:

  • 尝试获取用户 在xml中指定的下一个获取焦点的View
  • 通过遍历子View,将可以 获取到下一个焦点的子View加入到备选集合 focusables
  • 通过上述的备选集合 focusables 筛选出下一个获取焦点的View

用户在XML指定下一个获取焦点的View

用户可以在XML中指定不同方向下,基于该View的下一个获取焦点的View。

android:nextFocusLeft="@id/xxx"
android:nextFocusRight="@id/xxx"
android:nextFocusDown="@id/xxx"
android:nextFocusUp="@id/xxx"

获取焦点获取备选集合

如果用户未指定下一个获取焦点的View,则会通过备选集合的方式筛选出下一个获取焦点的View。首先需要获取到这个备选集合。

  • 先来看看View的实现:
// View.java
    public void addFocusables(ArrayList<View> views, @FocusDirection int direction) {
        addFocusables(views, direction, isInTouchMode() ? FOCUSABLES_TOUCH_MODE : FOCUSABLES_ALL);
    public void addFocusables(ArrayList<View> views, @FocusDirection int direction,
            @FocusableMode int focusableMode) {
        if (views == null) {
            return;
        if (!canTakeFocus()) {
            return;
        if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE
                && !isFocusableInTouchMode()) {
            return;
        views.add(this);
    }

这里比较简单,如果 符合获取焦点的资格,就会将自己添加到集合当中 。ps:views即为获取焦点的备选集合。

  • 再来看看ViewGroup的实现:
// ViewGroup.java
@Override
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);
    // 1\. 若descendantFocusability为FOCUS_BLOCK_DESCENDANTS,则会拦截子View获取焦点
    if (descendantFocusability == FOCUS_BLOCK_DESCENDANTS) {
        if (focusSelf) {
            super.addFocusables(views, direction, focusableMode);
        return;
    if (blockFocusForTouchscreen) {
        focusableMode |= FOCUSABLES_TOUCH_MODE;
    // 2\. 若descendantFocusability为FOCUS_BEFORE_DESCENDANTS,则会优先将自己加入到集合
    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];
        // 3\. 只允许添加当前是Visible的子View
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
            children[count++] = child;
    FocusFinder.sort(children, 0, count, this, isLayoutRtl());
    for (int i = 0; i < count; ++i) {
        children[i].addFocusables(views, direction, focusableMode);
    // 4\. 若descendantFocusability为FOCUS_AFTER_DESCENDANTS,
    // 则如果没有子View符合条件时将自己添加到集合
    if ((descendantFocusability == FOCUS_AFTER_DESCENDANTS) && focusSelf
            && focusableCount == views.size()) {
        super.addFocusables(views, direction, focusableMode);
}

首先需要先科普一下ViewGroup获取焦点的3种策略

  • FOCUS_BEFORE_DESCENDANTS : 优先 于子View获取焦点
  • FOCUS_AFTER_DESCENDANTS : 当子View都不获取焦点时 ,才获取焦点
  • FOCUS_BLOCK_DESCENDANTS : 禁止 子View获取焦点

同样可以通过XML设置,譬如:

android:descendantFocusability="beforeDescendants"

而这里就是根据不同的策略对集合的添加进行控制:

  • descendantFocusability = FOCUS_BLOCK_DESCENDANTS ,则会拦截子View获取焦点,见注释1处
  • descendantFocusability = FOCUS_BEFORE_DESCENDANTS ,则会优先将自己加入到集合,见注释2处
  • 遍历子View,并调用它的addFocusables,如果是ViewGroup的话就会起到往下递归目的,见注释3处
  • descendantFocusability = FOCUS_AFTER_DESCENDANTS 的话,则如果没有子View符合条件时将自己添加到集合,见注释4处

焦点记忆技巧

如果需要做 焦点记忆 的需求,就可以 考虑在 addFocusables 这里下手 了,譬如在RecyclerView中记录当前选中的位置,在 addFocusables 只将该位置对应的View添加到集合 ,这样就能将焦点重新给到这个View。

override fun addFocusables(views: ArrayList<View>?, direction: Int, focusableMode: Int) {
    // 根据选中位置,重新获取到View
    val view: View? = layoutManager?.findViewByPosition(currentSelectedPosition)
    if (hasFocus() || currentSelectedPosition < 0 || view == null) {
        super.addFocusables(views, direction, focusableMode)
    } else if (view.isFocusable && view.visibility == View.VISIBLE) {
        // 只将这个View添加到集合,并终止方法往下递归
        views?.add(view)
    } else {
        super.addFocusables(views, direction, focusableMode)
}

寻找下一个焦点

// FocusFinder.java
private View findNextFocus(ViewGroup root, View focused, Rect focusedRect,
        int direction, ArrayList<View> focusables) {
    if (focused != null) {
        if (focusedRect == null) {
            focusedRect = mFocusedRect;
        // 1\. 获取到focused所在区域,并将其换算成与根视图相同的坐标系
        focused.getFocusedRect(focusedRect);
        root.offsetDescendantRectToMyCoords(focused, focusedRect);
    } else {
        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:
            // 2\. 根据focused所在区域获取到下一个获取焦点的View
            return findNextFocusInAbsoluteDirection(focusables, root, focused,
                    focusedRect, direction);
        default:
            throw new IllegalArgumentException("Unknown direction: " + direction);
}

焦点寻找是 就近原则 ,默认会根据对比两块Rect区域的位置,来确定焦点位置。所以 findNextFocus 做了两件事:

  • 求出 当前拥有焦点的View即focused的Rect区域 ,并将它 转换成与根视图root相同的坐标系 。ps:这样是为了后续比较时能在同一坐标系下比较。
  • 根据focused所在区域获取到下一个获取焦点的View
// FocusFinder.java
View findNextFocusInAbsoluteDirection(ArrayList<View> focusables, ViewGroup root, View focused,
        Rect focusedRect, int direction) {
    // 1\. 最佳候选View所在的区域,初始值为focused的区域
    mBestCandidateRect.set(focusedRect);
    switch(direction) {
        case View.FOCUS_LEFT:
            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;
    // 2\. 遍历候选集合,筛选出最佳候选区域
    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;
        // 3\. 获取候选的View所在区域,并将它转换为与root相同坐标系
        focusable.getFocusedRect(mOtherRect);
        root.offsetDescendantRectToMyCoords(focusable, mOtherRect);
        // 4\. 对比两个区域,如果可行,就更新最佳候选View所在的区域
        if (isBetterCandidate(direction, focusedRect, mOtherRect, mBestCandidateRect)) {
            mBestCandidateRect.set(mOtherRect);
            closest = focusable;
    return closest;
}

mBestCandidateRect 最佳候选View的区域 ,这里大概分几步:

  • mBestCandidateRect 初始化为当前拥有焦点的区域 focusedRect
  • 通过遍历候选集合,筛选出最佳候选区域,与其对应的View对象
  • 在对比前需要将候选的View所在区域转换成与root相同坐标系
  • 对比区域可行后,会更新 mBestCandidateRect ,以及对应的View对象,整个过程 类似求最大值 的过程。

至此,下一个获取焦点的View就得出来了。

获取焦点

requestFocus

View#requestFocus

View获取焦点的流程

// View.java
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
    return requestFocusNoSearch(direction, previouslyFocusedRect);
private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {
    // need to be focusable
    if (!canTakeFocus()) {
        return false;
    // 在TouchMode下需要声明focusableInTouchMode为true才能获取焦点
    if (isInTouchMode() &&
        (FOCUSABLE_IN_TOUCH_MODE != (mViewFlags & FOCUSABLE_IN_TOUCH_MODE))) {
           return false;
    // 如果存在descendantFocusability为FOCUS_BLOCK_DESCENDANTS的父View则不能获取焦点
    if (hasAncestorThatBlocksDescendantFocus()) {
        return false;
    if (!isLayoutValid()) {
        mPrivateFlags |= PFLAG_WANTS_FOCUS;
    } else {
        clearParentsWantFocus();
    // 获取焦点
    handleFocusGainInternal(direction, previouslyFocusedRect);
    return true;
void handleFocusGainInternal(@FocusRealDirection int direction, Rect previouslyFocusedRect) {
    if (DBG) {
        System.out.println(this + " requestFocus()");
    if ((mPrivateFlags & PFLAG_FOCUSED) == 0) {
        mPrivateFlags |= PFLAG_FOCUSED;  // 1\. 标记为获取了焦点
        View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null;
        if (mParent != null) {
            // 2\. 通知自己的父View自己获取了焦点
            mParent.requestChildFocus(this, this);
            updateFocusedInCluster(oldFocus, direction);
        if (mAttachInfo != null) {
            // 3\. 回调viewTreeObserver的OnGlobalFocusChange
            mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
        // 4\. 如果有注册OnFocusChangeListener,会有回调
        onFocusChanged(true, direction, previouslyFocusedRect);
        refreshDrawableState();
}

View#requestFocus 最后会调用到 handleFocusGainInternal 方法

  • 更新 mPrivateFlags ,标记为自己已经获取了焦点
  • 调用 ViewGroup#requestChildFocus ,通知自己的父View自己获取了焦点
  • 回调相关监听
    • viewTreeObserver的OnGlobalFocusChange
    • OnFocusChangeListener.onFocusChange


ViewGroup#requestChildFocus

// ViewGroup.java
@Override
public void requestChildFocus(View child, View focused) {
    if (DBG) {
        System.out.println(this + " requestChildFocus()");
    // 如果descendantFocusability为FOCUS_BLOCK_DESCENDANTS则直接返回
    if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
        return;
    // Unfocus us, if necessary
    super.unFocus(focused);
    // 1\. 清空旧的焦点
    if (mFocused != child) {
        if (mFocused != null) {
            mFocused.unFocus(focused);
        mFocused = child;
    // 2\. 继续往父View方向传递
    if (mParent != null) {
        mParent.requestChildFocus(this, focused);
}

ViewGroup#requestChildFocus 主要做了两件事:

  • 清空旧的焦点状态
  • 继续调用父View的 requestChildFocus ,目的是 继续更新父View的记录状态

ViewGroup#requestFocus

ViewGroup会对requestFocus进行重写

// ViewGroup.java
@Override
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
    if (DBG) {
        System.out.println(this + " ViewGroup.requestFocus direction="
                + direction);
    int descendantFocusability = getDescendantFocusability();
    boolean result;
    switch (descendantFocusability) {
        // 1\. 不允许子View获取焦点
        case FOCUS_BLOCK_DESCENDANTS:
            result = super.requestFocus(direction, previouslyFocusedRect);
            break;
        // 2\. 自己优先于子View获取焦点    
        case FOCUS_BEFORE_DESCENDANTS: {
            final boolean took = super.requestFocus(direction, previouslyFocusedRect);
            result = took ? took : onRequestFocusInDescendants(direction,
                    previouslyFocusedRect);
            break;
        // 3\. 子View优先于自己获取焦点
        case FOCUS_AFTER_DESCENDANTS: {
            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;
// 4\. 寻找可以获取焦点的子View
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) {
            if (child.requestFocus(direction, previouslyFocusedRect)) {
                return true;
    return false;
}

onRequestFocusInDescendants 的作用是 寻找一个子View获取焦点 ,见注释4处

这里主要是扩展了获取焦点策略的能力,也是根据定义

  • FOCUS_BLOCK_DESCENDANTS 不允许 子View获取焦点,所以这里直接令自己去获取焦点,见注释1处
  • FOCUS_BEFORE_DESCENDANTS :自己 优先 于子View获取焦点,所以这里会先自己调用 super.requestFocus ,再通过结果调用 onRequestFocusInDescendants ,见注释2处
  • FOCUS_AFTER_DESCENDANTS 子View优先 于自己获取焦点,所以会先通过 onRequestFocusInDescendants 返回的结果,再视情况令自己获得焦点,见注释3处

开发中,可以重写 onRequestFocusInDescendants 以此来控制您想希望获取焦点的子View获取焦点

默认获取焦点

回到刚开始的流程,如果目前还没有找到拥有焦点的View,会怎样呢?

// ViewRootImpl.ViewPostImeInputStage.java#performFocusNavigation
View focused = mView.findFocus();
if (focused != null) {
} else {
    if (mView.restoreDefaultFocus()) {
        return true;