Android TV 按键焦点事件分发流程详解

ViewRootImpl中的类部类ViewPostImeInputStage.processKeyEvent(QueuedInputEvent q)--->DecorView.dispatchKeyEvent(event)
--->Activity.dispatchKeyEvent(event)

dispatchKeyEvent()执行流程

DecorView →PhoneWindow →Activity→ViewGroup→view

下面我们根据按键事件的分发流程,抽丝剥茧,逐一分析。

一、按键事件的入口:

private int processKeyEvent(QueuedInputEvent q)

ViewRootImpl.java-->ViewPostImeInputStage.processKeyEvent(QueuedInputEvent q)
private int processKeyEvent(QueuedInputEvent q) {
            final KeyEvent event = (KeyEvent)q.mEvent;

            if (mUnhandledKeyManager.preViewDispatch(event)) {
                return FINISH_HANDLED;
            }

            //分发按键事件到视图树,mView即是DecorView
            //DecorView继承FrameLayout,FrameLayout继承ViewGroup,
            // DecorView.dispatchKeyEvent(event)重写了父类ViewGroup.dispatchKeyEvent(event)方法
            // Deliver the key to the view hierarchy.
            if (mView.dispatchKeyEvent(event)) {
                return FINISH_HANDLED;
            }

            if (shouldDropInputEvent(q)) {
                return FINISH_NOT_HANDLED;
            }

            // This dispatch is for windows that don't have a Window.Callback. Otherwise,
            // the Window.Callback usually will have already called this (see
            // DecorView.superDispatchKeyEvent) leaving this call a no-op.
            if (mUnhandledKeyManager.dispatch(mView, event)) {
                return FINISH_HANDLED;
            }

            int groupNavigationDirection = 0;

            if (event.getAction() == KeyEvent.ACTION_DOWN
                    && event.getKeyCode() == KeyEvent.KEYCODE_TAB) {
                if (KeyEvent.metaStateHasModifiers(event.getMetaState(), KeyEvent.META_META_ON)) {
                    groupNavigationDirection = View.FOCUS_FORWARD;
                } else if (KeyEvent.metaStateHasModifiers(event.getMetaState(),
                        KeyEvent.META_META_ON | KeyEvent.META_SHIFT_ON)) {
                    groupNavigationDirection = View.FOCUS_BACKWARD;
                }
            }

            // If a modifier is held, try to interpret the key as a shortcut.
            if (event.getAction() == KeyEvent.ACTION_DOWN
                    && !KeyEvent.metaStateHasNoModifiers(event.getMetaState())
                    && event.getRepeatCount() == 0
                    && !KeyEvent.isModifierKey(event.getKeyCode())
                    && groupNavigationDirection == 0) {
                if (mView.dispatchKeyShortcutEvent(event)) {
                    return FINISH_HANDLED;
                }
                if (shouldDropInputEvent(q)) {
                    return FINISH_NOT_HANDLED;
                }
            }

            // Apply the fallback event policy.
            if (mFallbackEventHandler.dispatchKeyEvent(event)) {
                return FINISH_HANDLED;
            }
            if (shouldDropInputEvent(q)) {
                return FINISH_NOT_HANDLED;
            }

            // Handle automatic focus changes.
            //如果是按键按下事件,则处理焦点自动导航
            if (event.getAction() == KeyEvent.ACTION_DOWN) {
                if (groupNavigationDirection != 0) {
                    if (performKeyboardGroupNavigation(groupNavigationDirection)) {
                        return FINISH_HANDLED;
                    }
                } else {
                    //执行焦点导航逻辑
                    if (performFocusNavigation(event)) {
                        return FINISH_HANDLED;
                    }
                }
            }
            return FORWARD;
        }
二、dispatchKeyEvent(KeyEvent event) 在DecorView、ViewGroup、View中的分发流程

1、DecorView.java

DecorView.java

 @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        final int keyCode = event.getKeyCode();
        final int action = event.getAction();
        final boolean isDown = action == KeyEvent.ACTION_DOWN;

        if (isDown && (event.getRepeatCount() == 0)) {
            // First handle chording of panel key: if a panel key is held
            // but not released, try to execute a shortcut in it.
            if ((mWindow.mPanelChordingKey > 0) && (mWindow.mPanelChordingKey != keyCode)) {
                boolean handled = dispatchKeyShortcutEvent(event);
                if (handled) {
                    return true;
                }
            }

            // If a panel is open, perform a shortcut on it without the
            // chorded panel key
            if ((mWindow.mPreparedPanel != null) && mWindow.mPreparedPanel.isOpen) {
                if (mWindow.performPanelShortcut(mWindow.mPreparedPanel, keyCode, event, 0)) {
                    return true;
                }
            }
        }

        if (!mWindow.isDestroyed()) {
            //DecorView中的mWindow唯一实现类就是PhoneWindow,而Activity则是Window.Callback是实现类。Activity中调用mWindow.setCallback(this)
            //故而,此处mWindow.getCallback()获取到的就是Activity实例,调用cb.dispatchKeyEvent(event)实际上就是调用到Activity.dispatchKeyEvent(event)
            //Activity会在其dispatchKeyEvent(event)方法中做一些按键事件分发处理。如onKeyDown、onKeyUp、onKeyLongPress、onKeyMultiple方法的回调
            //这些回调方法中,如果消费了事件,则返回true,结束事件继续分发;如果不消费事件,则返回false,继续调用父类super.dispatchKeyEvent(event)方法层层递归进行事件分发
            final Window.Callback cb = mWindow.getCallback();
            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);
    }

2、Activity.java

Activity.java

 /**
     * Called to process key events.  You can override this to intercept all
     * key events before they are dispatched to the window.  Be sure to call
     * this implementation for key events that should be handled normally.
     *
     * @param event The key event.
     *
     * @return boolean Return true if this event was consumed.
     */
    public boolean dispatchKeyEvent(KeyEvent event) {
        onUserInteraction();

        // Let action bars open menus in response to the menu key prioritized over
        // the window handling it
        final int keyCode = event.getKeyCode();
        if (keyCode == KeyEvent.KEYCODE_MENU &&
                mActionBar != null && mActionBar.onMenuKeyEvent(event)) {
            return true;
        }

        Window win = getWindow();
        if (win.superDispatchKeyEvent(event)) {
            return true;
        }
        View decor = mDecor;
        if (decor == null) decor = win.getDecorView();
        //调用KeyEvent event对象的dispatch(Callback receiver, DispatcherState state,Object target)方法
        //把事件接收的处理者作为reciver传入,进行事件的处理。例如,调用事件接收者的onKeyDown、onKeyUp、onKeyLongPress、onKeyMultiple方法的回调
        //事件处理的结果会向上层回传给DecorView.dispatchKeyEvent()-->mView.dispatchKeyEvent(event)
        // 如果返回true,则消费事件,直接将结果回传到ViewRootImpl中的mView.dispatchKeyEvent(event);
        // 如果返回false,则DecorView会调用super.dispatchKeyEvent(event)层层递归分发事件直到返回结果。
        //
        return event.dispatch(this, decor != null
                ? decor.getKeyDispatcherState() : null, this);
    }

3、ViewGroup.java

ViewGroup.java

 @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onKeyEvent(event, 1);
        }

        if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
                == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
            if (super.dispatchKeyEvent(event)) {
                return true;
            }
        } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
                == PFLAG_HAS_BOUNDS) {
            //调用当前获得焦点或包含焦点的mFocused.dispatchKeyEvent(event)
            if (mFocused.dispatchKeyEvent(event)) {
                //如何当前焦点view消费了该事件,则返回true,结束事件分发后续流程。
                return true;
            }
        }

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

4、View.java

View.java

 /**
     * Dispatch a key event to the next view on the focus path. This path runs
     * from the top of the view tree down to the currently focused view. If this
     * view has focus, it will dispatch to itself. Otherwise it will dispatch
     * the next node down the focus path. This method also fires any key
     * listeners.
     *
     * @param event The key event to be dispatched.
     * @return True if the event was handled, false otherwise.
     */
    public boolean dispatchKeyEvent(KeyEvent event) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onKeyEvent(event, 0);
        }

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

        //将view自己作为事件接收者,传入KeyEvent event对象,处理事件的分发
        if (event.dispatch(this, mAttachInfo != null
                ? mAttachInfo.mKeyDispatchState : null, this)) {
            //如果事件被消费掉了,则返回true,直接返回到上层mView.dispatchKeyEvent(event)结束事件传递
            return true;
        }
       
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        //如事件没有被消费掉,返回false
        return false;
    }

三、KeyEvent中dispatch(Callback receiver, DispatcherState state,Object target)如果将事件分发给接收者receiver中的各个回调方法

通过该方法,接收器receiver的onKeyDown、onKeyUp、onKeyLongPress、onKeyMultiple等方法将被回调。

/**
     * Deliver this key event to a {@link Callback} interface.  If this is
     * an ACTION_MULTIPLE event and it is not handled, then an attempt will
     * be made to deliver a single normal event.
     *
     * @param receiver The Callback that will be given the event.
     * @param state State information retained across events.
     * @param target The target of the dispatch, for use in tracking.
     *
     * @return The return value from the Callback method that was called.
     */
    public final boolean dispatch(Callback receiver, DispatcherState state,
            Object target) {
        switch (mAction) {
            case ACTION_DOWN: {
                mFlags &= ~FLAG_START_TRACKING;
                if (DEBUG) Log.v(TAG, "Key down to " + target + " in " + state
                        + ": " + this);
                boolean res = receiver.onKeyDown(mKeyCode, this);
                if (state != null) {
                    if (res && mRepeatCount == 0 && (mFlags&FLAG_START_TRACKING) != 0) {
                        if (DEBUG) Log.v(TAG, "  Start tracking!");
                        state.startTracking(this, target);
                    } else if (isLongPress() && state.isTracking(this)) {
                        try {
                            if (receiver.onKeyLongPress(mKeyCode, this)) {
                                if (DEBUG) Log.v(TAG, "  Clear from long press!");
                                state.performedLongPress(this);
                                res = true;
                            }
                        } catch (AbstractMethodError e) {
                        }
                    }
                }
                return res;
            }
            case ACTION_UP:
                if (DEBUG) Log.v(TAG, "Key up to " + target + " in " + state
                        + ": " + this);
                if (state != null) {
                    state.handleUpEvent(this);
                }
                return receiver.onKeyUp(mKeyCode, this);
            case ACTION_MULTIPLE:
                final int count = mRepeatCount;
                final int code = mKeyCode;
                if (receiver.onKeyMultiple(code, count, this)) {
                    return true;
                }
                if (code != KeyEvent.KEYCODE_UNKNOWN) {
                    mAction = ACTION_DOWN;
                    mRepeatCount = 0;
                    boolean handled = receiver.onKeyDown(code, this);
                    if (handled) {
                        mAction = ACTION_UP;
                        receiver.onKeyUp(code, this);
                    }
                    mAction = ACTION_MULTIPLE;
                    mRepeatCount = count;
                    return handled;
                }
                return false;
        }
        return false;
    }

三、焦点导航

在上述按键事件的入口中提到的ViewRootImpl中

            //分发按键事件到视图树,mView即是DecorView
            //DecorView继承FrameLayout,FrameLayout继承ViewGroup,
            // DecorView.dispatchKeyEvent(event)重写了父类ViewGroup.dispatchKeyEvent(event)方法
            // Deliver the key to the view hierarchy.
            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 {
                    //执行焦点导航逻辑
                    if (performFocusNavigation(event)) {
                        return FINISH_HANDLED;
                    }
                }
            }

如果mView.dispatchKeyEvent(event)返回true,则结束事件分发;
如果返回false,则调用如下方法

                    //执行焦点导航逻辑
                    if (performFocusNavigation(event)) {
                        return FINISH_HANDLED;
                    }

继续执行后续的焦点导航流程。
焦点导航的总体流程就是:
1、View focused = mView.findFocus();//从视图树的顶层,即DecorView一层一层的递归查找当前获得焦点的view
2、View v = focused.focusSearch(direction);根据导航的方向查找下一个可获取焦点的view
3、v.requestFocus(direction, mTempRect)请求获取焦点
4、v.requestFocus(direction,mTempRect)内部,调用mParent.requestChildFocus(this, focused)逐层递归向上级通知

ViewRootImpl.java

/**
 * 系统焦点自动导航逻辑
 * 注意:以下所有的焦点导航逻辑都是在KeyEvent.ACTION_DOWN下执行
 * **/
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;
            //TAB键
            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) {
            //mView:即DecorView,DecorView是整个ViewTree的最顶层View,它是一个FrameLayout布局,代表了整个应用的界面.
            View focused = mView.findFocus();//从视图树的顶层,即DecorView一层一层的递归查找当前获得焦点的view
            if (focused != null) {
                //找到了当前获得焦点的focused,调用该焦点view的focusSearch(direction)方法查找direction方向上下一个将要获取焦点的view
                //focused.focusSearch(direction)实际上会调用mParent.focusSearch(this, direction)方法,层层递归,直到调用到DecorView的focusSearch(this, direction)方法。
                //而DecorView继承ViewGroup,实际上最终会调用到FocusFinder.getInstance().findNextFocus(this, focused, direction),this 就是DecorView对象
                View v = focused.focusSearch(direction);
                if (v != null && v != focused) {//找到下一个可获取焦点view,并且不是当前获取焦点的view
                    // 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);
                    }
                    //让找到的下一个可获取焦点的view获取焦点
                    //该v可能是view或者是ViewGroup。 ViewGroup获取焦点的最大区别就是重写了View.requestFocus(int direction, Rect previouslyFocusedRect)方法。
                    //ViewGroup会根据指定的焦点策略(FOCUS_BEFORE_DESCENDANTS、FOCUS_AFTER_DESCENDANTS、FOCUS_BLOCK_DESCENDANTS)来处理焦点在自己和子View之间的流转。
                    if (v.requestFocus(direction, mTempRect)) {//获取到焦点后,该焦点view会层层上报自己的parent,让parent做出相应的响应
                        //播放声音
                        playSoundEffect(SoundEffectConstants
                                .getContantForFocusDirection(direction));
                        //新的view已经获取到了焦点,返回true消费事件,事件不再往下继续执行。
                        return true;
                    }
                }

                // Give the focused view a last chance to handle the dpad key.
                //给当前获取焦点的focused view 最后一次处理事件的机会
                //即,focused.focusSearch(direction)查找的下个可获取焦点的view 为null时 获取前面的事件未被消费调,会获得执行机会
                if (mView.dispatchUnhandledMove(focused, direction)) {
                    //从DecorView开始,递归调用dispatchUnhandledMove(focused, direction),直到focused中的dispatchUnhandledMove(focused, direction)
                    return true;
                }
            } else {
                //递归调用,重置默认焦点(整个视图树上只能有唯一一个默认焦点view)
                if (mView.restoreDefaultFocus()) {
                    return true;
                }
            }
        }
        return false;
    }
        
1、获取当前获得焦点view

mView即DecorView,从DecorView开始,一层一层的向下递归查找当前获得焦点的view

View focused = mView.findFocus()
2、搜索下一个在指定方向上可获取焦点的view

找到了当前获得焦点的focused,调用该焦点view的focusSearch(direction)方法查找direction方向上下一个将要获取焦点的view。
focused.focusSearch(direction)实际上会调用mParent.focusSearch(this, direction)方法,层层递归,直到调用到DecorView的focusSearch(this, direction)方法。
而DecorView继承ViewGroup,实际上最终会调用到FocusFinder.getInstance().findNextFocus(this, focused, direction),this 就是DecorView对象。

View v = focused.focusSearch(direction)

最终会调用到DecorView父类ViewGroup中的FocusFinder.getInstance().findNextFocus(this, focused, direction);

ViewGroup.java

ViewGroup.java
 /**
     * Find the nearest view in the specified direction that wants to take
     * focus.
     *
     * @param focused   The view that currently has focus
     * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and
     *                  FOCUS_RIGHT, or 0 for not applicable.
     */
    @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;
    }

FocusFinder.java

FocusFinder.java

    /**
     * 在指定的root ViewGroup上,更加当前传入的获得焦点的view,查找direction方向上可获取焦点的下一个view。
     *
     * root 如果是DecoverView,则是在整个视图树中查找;root 如果是其它ViewGroup,则是在该ViewGroup上查找;
     *
     * 即 root,限定的焦点在视图树上查找的范围。
     *
     * Find the next view to take focus in root's descendants, starting from the view
     * that currently is focused.
     * @param root Contains focused. Cannot be null.
     * @param focused Has focus now.  当前获取焦点的view
     * @param direction Direction to look. 查找的方向
     * @return The next focusable view, or null if none exists.
     */
    public final View findNextFocus(ViewGroup root, View focused, int direction) {
        return findNextFocus(root, focused, null, direction);
    }


    private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
        View next = null;
        ViewGroup effectiveRoot = getEffectiveRoot(root, focused);
        if (focused != null) {
            //优先查找用户指定的下一个可获取焦点的view
            //即用户在XML中或代码中指定的下一个获取焦点view的ID值来查找
            next = findNextUserSpecifiedFocus(effectiveRoot, focused, direction);
        }
        if (next != null) {
            //如果找到用户指定的获取下一个焦点的view,则直接返回
            return next;
        }
        ArrayList<View> focusables = mTempList;
        try {
            focusables.clear();
            //添加effectiveRoot下的所有view到focusables集合中去
            //重写ViewGroup的该方法,可以实现焦点记忆功能
            effectiveRoot.addFocusables(focusables, direction);
            if (!focusables.isEmpty()) {
                //根据系统默认的就近原则算法,查找下一个direction方向上可获取焦点的最近的view
                next = findNextFocus(effectiveRoot, focused, focusedRect, direction, focusables);
            }
        } finally {
            focusables.clear();
        }
        return next;
    }
3、请求获取焦点

搜索到下一个获取焦点的view后,调用该view.requestFocus(direction, mTempRect)方法
注意:调用requestFocus(direction, mTempRect)需要区分调用者。
如果是ViewGroup,则会更加焦点获取策略,实现父View和子View之间获取焦点的优先级。
如下是ViewGroup.java 和View.java 中requestFocus方法是实现:

ViewGroup.java

ViewGroup.java

    /**
     * {@inheritDoc}
     *
     * ViewGroup请求焦点的方法
     *
     * ViewGroup重新了View的该方法,实现了获取焦点顺序的拦截。可根据不同的策略,实现父View和子View之间获取焦点的优先级。
     * <p>
     * 通过FOCUS_BEFORE_DESCENDANTS,父View优先获取焦点,配合重写addFocusables()方法,实现焦点记忆功能
     * <p>
     * Looks for a view to give focus to respecting the setting specified by
     * {@link #getDescendantFocusability()}.
     * <p>
     * Uses {@link #onRequestFocusInDescendants(int, android.graphics.Rect)} to
     * find focus within the children of this group when appropriate.
     *
     * @see #FOCUS_BEFORE_DESCENDANTS
     * @see #FOCUS_AFTER_DESCENDANTS
     * @see #FOCUS_BLOCK_DESCENDANTS
     * @see #onRequestFocusInDescendants(int, android.graphics.Rect)
     */
    @Override
    public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
        if (DBG) {
            System.out.println(this + " ViewGroup.requestFocus direction="
                    + direction);
        }
        int descendantFocusability = getDescendantFocusability();

        //以下代码如果是调用super.requestFocus(direction, previouslyFocusedRect),表示viewGroup自己处理焦点事件
        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: {
                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;
    }

View.java

View.java

    public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
        return requestFocusNoSearch(direction, previouslyFocusedRect);
    }

    //直接请求获取焦点,不再搜索
    private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {
        //在该方法中会对控件的当前状态进行判断, 如果不符合获取焦点的前提则直接返回false告知调用方, 控件不会获取焦点
        //只要符合前提就会继续执行, 最终必定返回true, 不论当前控件的焦点状态是否有改变
        // need to be focusable
        if (!canTakeFocus()) {
            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
        //如果该View的父View阻止其获取焦点,则直接返回false
        if (hasAncestorThatBlocksDescendantFocus()) {
            return false;
        }

        if (!isLayoutValid()) {
            mPrivateFlags |= PFLAG_WANTS_FOCUS;
        } else {
            clearParentsWantFocus();
        }

        //framework内部处理获得焦点核心
        handleFocusGainInternal(direction, previouslyFocusedRect);
        return true;
    }



    /**
     *
     * framework处理获取焦点的内部核心逻辑
     * Give this view focus. This will cause
     * {@link #onFocusChanged(boolean, int, android.graphics.Rect)} to be called.
     *
     * Note: this does not check whether this {@link View} should get focus, it just
     * gives it focus no matter what.  It should only be called internally by framework
     * code that knows what it is doing, namely {@link #requestFocus(int, Rect)}.
     *
     * @param direction values are {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
     *        {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT}. This is the direction which
     *        focus moved when requestFocus() is called. It may not always
     *        apply, in which case use the default View.FOCUS_DOWN.
     * @param previouslyFocusedRect The rectangle of the view that had focus
     *        prior in this View's coordinate system.
     */
    void handleFocusGainInternal(@FocusRealDirection int direction, Rect previouslyFocusedRect) {
        if (DBG) {
            System.out.println(this + " requestFocus()");
        }

        if ((mPrivateFlags & PFLAG_FOCUSED) == 0) {
            mPrivateFlags |= PFLAG_FOCUSED;

            //getRootView() 获取到的是DecorView,即在整个视图树中查找当前已经获取焦点的view
            View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null;

            if (mParent != null) {
                //层层向上递归,通知父view 我获取了焦点
                mParent.requestChildFocus(this, this);
                updateFocusedInCluster(oldFocus, direction);
            }

            if (mAttachInfo != null) {
                //通知View树上焦点全局监听者
                mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
            }

            //通知view自己获得焦点,重写该方法,可监听view自己焦点状态变更
            onFocusChanged(true, direction, previouslyFocusedRect);
            //刷新view drawable状态
            refreshDrawableState();
        }
    }

4、焦点变更逐层递归向上级通知

View获取到焦点后,会调用mParent.requestChildFocus(this, focused)逐层递归向上级通知
ViewGroup.java

    @Override
    public void requestChildFocus(View child, View focused) {
        //如果入参 child==focused 为true,即代表 this 是focused 的直接父view
        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) {
                //如果之前有view获取焦点,取消焦点
                mFocused.unFocus(focused);
            }

            //记录获得焦点的view或包含焦点的viewGroup
            mFocused = child;
        }
        if (mParent != null) {
            //层层向上递归,通知父view,我的子view获取了焦点
            //如果入参 child==focused 为true,即代表 this 是focused 的直接父view
            mParent.requestChildFocus(this, focused);
        }
    }

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 155,541评论 4 358
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 65,987评论 1 285
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 105,387评论 0 236
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,325评论 0 201
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,657评论 3 284
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,125评论 1 202
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,519评论 2 305
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,242评论 0 193
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,863评论 1 234
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,198评论 2 239
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,747评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,106评论 2 249
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,635评论 3 227
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,926评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,631评论 0 190
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,131评论 2 261
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,083评论 2 257

推荐阅读更多精彩内容