关于 compose-focus-navigation
compose-focus-navigation 是一项面向 Jetpack Compose 开发者的焦点导航 skill,指导如何配置并测试键盘、D-pad 或遥控器操作。它强调显式处理例外焦点路径,并通过用户实际使用的输入验证焦点状态。
主要功能
- 01按需添加焦点请求与状态观察
- 02仅在默认空间搜索不合适时配置例外路径
- 03只处理需要自定义行为的按键事件
- 04刷新后按语义标识恢复焦点
- 05使用按键输入和焦点语义测试
适用场景
- 01设置初始焦点或恢复焦点
- 02修正特定方向上的焦点跳转
- 03为刷新后的列表选择稳定焦点
- 04验证键盘或 D-pad 导航行为
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ chrisbanes/ skills --skill compose-focus-navigation安装后在 Claude Code 中输入/compose-focus-navigation确认已加载。
详细介绍
Compose:焦点导航
核心原则
焦点是有状态的 UI 行为:明确目标和特殊边缘,再通过用户的键盘、方向键或遥控器输入来驱动并验证。
流程
- 从已支持焦点的组件开始。仅在需要实现特定行为时添加钩子:
| 需求 | 添加 |
|---|---|
| 普通按钮/文本字段/可点击组件的焦点 | 无需额外添加;使用可聚焦组件 |
| 以编程方式设置初始/恢复焦点 | FocusRequester + Modifier.focusRequester(...) |
| 对焦点变化做出视觉或状态响应 | Modifier.onFocusChanged { ... } |
| 尚不支持焦点的自定义交互界面 | Modifier.focusable(),并酌情添加角色/语义 |
- 从
LaunchedEffect请求初始焦点或恢复焦点,并将其键设为目标可用的条件。对于惰性内容,按稳定的项目 ID 保存请求器,并仅在项目完成组合后请求焦点。在AnimatedContent中,渲染标识、标签、请求器归属和 effect 键都应始终使用内容 lambda 的目标;捕获外部状态会导致退出和进入的内容具有相同标识。 在代码审查中,如果指出在可组合函数主体中运行requestFocus(),那么在说明应通过哪个事件或键控 effect 于目标可用后发出请求之前,这条审查意见并不完整。 - 保留默认的空间搜索,除非某条具体边缘、跳转或陷阱存在问题。仅使用
focusProperties编码这些例外。 - 仅为非普通点击或焦点遍历行为处理按键。只消费实际处理的事件;在开销较大的拥有方对快速方向键操作进行节流,而不是在整个屏幕范围内节流。
- 刷新后按语义标识恢复焦点:如果焦点所在 ID 仍存在,就保留它;否则选择一个确定性的回退目标。
- 使用一次具体的按键/方向键交互和焦点语义进行测试。审查的行为由用户触发时,不要用直接修改状态替代,也不要让输入成为条件性操作。仅使用屏幕截图验证焦点外观。
- 当所有有意设置的目标和特殊边缘都已编码、加载/刷新行为具有稳定的焦点策略,并且测试采用与用户相同的输入模型时,即可完成。
例如,只有在两种行为都必需时,才请求并观察焦点:
val requester = remember { FocusRequester() }
Button(
onClick = onClick,
modifier = Modifier
.focusRequester(requester)
.onFocusChanged { state -> isFocused = state.isFocused },
) {
Text("Play")
}
通过 effect 而非可组合函数主体请求焦点:
val initialFocus = remember { FocusRequester() }
LaunchedEffect(initialFocus) {
initialFocus.requestFocus()
}
如果目标在加载后才出现,请将请求的键设为相应条件:
LaunchedEffect(items.isNotEmpty()) {
if (items.isNotEmpty()) {
firstItemRequester.requestFocus()
}
}
仅当默认空间搜索有误时,才使用 focusProperties:
Modifier.focusProperties {
up = headerRequester
down = firstRowRequester
left = FocusRequester.Cancel
}
过多的硬编码链接会造成焦点图过时。对于特殊按键行为,只消费实际处理的事件:
Modifier.onPreviewKeyEvent { event ->
if (event.type == KeyEventType.KeyUp && event.key == Key.Back) {
onBack()
true
} else {
false
}
}
通过用户输入测试焦点:
composeTestRule.onNodeWithTag("screen").performKeyInput {
pressKey(Key.DirectionDown)
}
composeTestRule.onNodeWithTag("play-button").assertIsFocused()
更全面的测试结构选择请参阅 Compose UI 测试模式。
评论
支持 Markdown