如何使用 android:nextFocusDown、android:nextFocusRight 等属性优化遥控器导航?

解读

国内电视盒子、投影、车机、酒店电视等 Android 终端 90% 以上仍靠红外或蓝牙遥控器操作,焦点导航体验直接决定留存率。面试官问“怎么用 nextFocusXxx 优化”,不是考你背属性名,而是验证三件事:

  1. 能否在“无触摸”场景下快速定位焦点错乱根因;
  2. 是否理解焦点查找源码流程,能在复杂容器(RecyclerView、Fragment、自定义 ViewGroup)中给出兜底策略;
  3. 是否具备“量化验证 + 灰度兼容”意识,避免上线后“用户狂按遥控器却点不到按钮”的客诉。

知识点

  1. 焦点查找优先级:xml 显式 nextFocusId > 布局方向算法(FOCUS_DOWN/RIGHT/LEFT/UP)> 就近算法(Rect 距离)。
  2. 源码路径:
    ViewGroup.focusSearch() → FocusFinder.findNextFocus() → 先查 mFocused 的 nextFocusXxx,再走算法。
  3. 遥控器与触控差异:
    • 无 ACTION_DOWN 事件,只有 KeyEvent.KEYCODE_DPAD_XXX;
    • 16 ms 内若未调用 playSoundEffect/SoundEffectConstants,系统会播默认“滴答”音,可重写 View.playSoundEffect 做品牌定制。
  4. 国内常见坑:
    • 华为、小米盒子 5.x 以后对 RecyclerView 做焦点缓存,notifyDataSetChanged 后焦点被重置,需调用 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS) + setSelectedPosition 同步;
    • 广告弹窗 Dialog 默认在 DecorView 新 Window,nextFocus 链断裂,需在 onShow 中主动 requestFocus;
    • 折叠屏展开后,横屏遥控器导航方向感错乱,需监听 FoldingFeature 动态修改 nextFocusLeft/Right。
  5. 量化工具:
    adb shell input keyevent 19/20/21/22 模拟遥控器;
    systrace tag=input 查看 dispatchKeyEvent 耗时;
    埋点:记录 KeyEvent 到真正 onFocusChanged 耗时,> 200 ms 视为卡顿。

答案

“优化遥控器导航,我分四步落地:

  1. 静态声明:在 xml 对关键按钮显式写死 nextFocusDown/right 指向,避免算法误算;对 RecyclerView item 内焦点,用 android:descendantFocusability="afterDescendants" 让 item 内部优先。
  2. 动态兜底:重写 ViewGroup.focusSearch(View focused, int direction),当系统找不到 nextFocus 时,按业务权重返回指定按钮(如‘立即购买’),防止焦点消失。
  3. 边界兼容:
    • 广告或弹窗 new Dialog 时,在 onShow 中先 dialogView.requestFocus(),再对根节点设置 nextFocusDown 指向关闭按钮;
    • 折叠屏展开后,监听 WindowLayoutInfo,若 isSeparating 则把 nextFocusRight 重新指向右侧 Fragment 的顶部 Tab,保证方向感一致。
  4. 灰度验证:
    用内部云测平台下发脚本,循环 500 次随机 DPAD 事件,统计焦点丢失率 < 0.1%;
    线上埋点监控 KeyEvent→onFocusChanged 耗时,超过 200 ms 自动上报,回滚阈值 1%。

通过以上四步,我们在上一代盒子项目中把焦点丢失率从 2.3% 降到 0.05%,遥控器平均按键路径缩短 1.8 步,次日留存提升 1.2 个百分点。”

拓展思考

  1. 当业务升级到 Jetpack Compose TV 时,没有 xml 属性,可用 Modifier.focusRestorer() + FocusRequester 实现等效 nextFocusId,同时利用 bringIntoView() 解决列表滚动后焦点被遮挡问题。
  2. 对于 5G 车机多屏互动,副驾屏与主驾屏属于不同 Display,FocusFinder 默认只搜当前 Display,需自定义 FocusFinder 实例并注入到 InputManager,实现跨屏焦点链。
  3. 隐私沙盒限制广告弹窗获取焦点,可提前在 AdServices 注册 FocusDelegationToken,让系统信任焦点跳转,否则 nextFocus 会被 SELinux 拒绝。