美文网首页TV开发
Android TV 按键焦点事件分发流程详解

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

作者: 踏雪鸿雁 | 来源:发表于2021-09-17 18:21 被阅读0次
    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);
            }
        }
    
    

    相关文章

      网友评论

        本文标题:Android TV 按键焦点事件分发流程详解

        本文链接:https://www.haomeiwen.com/subject/isinwltx.html