Agent Skillschrisbanes/skills › compose-focus-navigation

compose-focus-navigation

GitHub

指导Jetpack Compose TV/桌面端焦点导航开发,涵盖FocusRequester、D-pad及键盘事件处理,确保语义化焦点状态与测试验证。

skills/compose-focus-navigation/SKILL.md chrisbanes/skills

触发场景

编写或审查Jetpack Compose TV/桌面UI的焦点逻辑 实现D-pad键盘导航或初始焦点行为 处理焦点请求与状态变化

安装

npx skills add chrisbanes/skills --skill compose-focus-navigation -g -y
更多选项

不安装直接使用

npx skills use chrisbanes/skills@compose-focus-navigation

指定 Agent (Claude Code)

npx skills add chrisbanes/skills --skill compose-focus-navigation -a claude-code -g -y

安装 repo 全部 skill

npx skills add chrisbanes/skills --all -g -y

预览 repo 内 skill

npx skills add chrisbanes/skills --list

SKILL.md

Frontmatter
{
    "name": "compose-focus-navigation",
    "description": "Use when writing or reviewing Jetpack Compose UI for TV, keyboard, desktop, accessibility focus, D-pad navigation, FocusRequester, focusProperties, key events, or initial focus behavior."
}

Compose: focus navigation

Core principle

Focus is stateful UI behavior: make targets and exceptional edges explicit, then drive and verify it through the user's keyboard, D-pad, or remote input.

Procedure

  1. Start with components that already participate in focus. Add a hook only for a requested behavior:
Need Add
Normal button/text field/clickable focus Nothing extra; use the focusable component
Programmatic initial/restored focus FocusRequester + Modifier.focusRequester(...)
Visual or state reaction to focus changes Modifier.onFocusChanged { ... }
Custom interactive surface that is not already focusable Modifier.focusable() plus role/semantics as appropriate
  1. Request initial or restored focus from LaunchedEffect, keyed to the condition that makes the target present. For lazy content, keep requesters by stable item id and request only after the item is composed. Inside AnimatedContent, use the content lambda's target consistently for rendered identity, tags, requester ownership, and the effect key; captured outer state gives outgoing and incoming content the same identity.
  2. Keep default spatial search unless a concrete edge, jump, or trap is wrong. Encode only those exceptions with focusProperties.
  3. Handle keys only for behavior that is not normal click or traversal. Consume exactly the handled event; throttle rapid D-pad work at its expensive owner, not across the screen.
  4. Restore by semantic identity after refresh: retain the focused id when it exists, otherwise choose a deterministic fallback.
  5. Test with one concrete key/D-pad interaction and focused semantics. When the behavior under review is user-triggered, do not substitute direct state mutation or leave the input conditional. Use screenshots only for the focus appearance.
  6. Finish when all intentional targets and exceptional edges are encoded, loading/refresh behavior has a stable focus policy, and tests use the same input model as users.

For example, request and observe focus only when both behaviors are required:

val requester = remember { FocusRequester() }

Button(
    onClick = onClick,
    modifier = Modifier
        .focusRequester(requester)
        .onFocusChanged { state -> isFocused = state.isFocused },
) {
    Text("Play")
}

Call focus requests from an effect, not the composable body:

val initialFocus = remember { FocusRequester() }

LaunchedEffect(initialFocus) {
    initialFocus.requestFocus()
}

If the target appears after loading, key the request to the condition:

LaunchedEffect(items.isNotEmpty()) {
    if (items.isNotEmpty()) {
        firstItemRequester.requestFocus()
    }
}

Use focusProperties only when default spatial search is wrong:

Modifier.focusProperties {
    up = headerRequester
    down = firstRowRequester
    left = FocusRequester.Cancel
}

Too many hard-coded links create stale focus graphs. For special key behavior, consume only the handled event:

Modifier.onPreviewKeyEvent { event ->
    if (event.type == KeyEventType.KeyUp && event.key == Key.Back) {
        onBack()
        true
    } else {
        false
    }
}

Test focus through user input:

composeTestRule.onNodeWithTag("screen").performKeyInput {
    pressKey(Key.DirectionDown)
}

composeTestRule.onNodeWithTag("play-button").assertIsFocused()

Broader test-shape choices are in Compose UI testing patterns.

版本历史

  • 2026.8.27 当前 2026-08-28 05:51
  • 2026.8.24 2026-08-27 17:15

    精简指令内容,减少冗余说明。

  • 2026.7.21 2026-07-24 12:24

同 Skill 集合

skills/compose-animations/SKILL.md
skills/compose-component-design/SKILL.md
skills/compose-modifier-and-layout-style/SKILL.md
skills/compose-performance/SKILL.md
skills/compose-recomposition-performance/SKILL.md
skills/compose-side-effects/SKILL.md
skills/compose-slot-api-pattern/SKILL.md
skills/compose-stability-diagnostics/SKILL.md
skills/compose-state-and-effects/SKILL.md
skills/compose-state-authoring/SKILL.md
skills/compose-state-deferred-reads/SKILL.md
skills/compose-state-hoisting/SKILL.md
skills/compose-state-holder-ui-split/SKILL.md
skills/compose-ui-testing-patterns/SKILL.md
skills/gradle-run/SKILL.md
skills/grounded-writing/SKILL.md
skills/implement-issue/SKILL.md
skills/implement-with-subagents/SKILL.md
skills/kotlin-api-design/SKILL.md
skills/kotlin-concurrency-and-flow/SKILL.md
skills/kotlin-control-flow/SKILL.md
skills/kotlin-coroutines-structured-concurrency/SKILL.md
skills/kotlin-flow-state-event-modeling/SKILL.md
skills/kotlin-functions/SKILL.md
skills/kotlin-multiplatform-expect-actual/SKILL.md
skills/kotlin-types-value-class/SKILL.md
skills/run-github-project/SKILL.md
skills/shepherd/SKILL.md
skills/to-plan/SKILL.md
skills/using-chrisbanes-skills/SKILL.md

元信息

文件数
0
版本
2026.8.27
Hash
b2626ba9
收录时间
2026-07-24 12:24

首页 - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-09-02 14:28
浙ICP备14020137号-1 $访客地图$