You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android TV Compose:打开ModalNavigationDrawer时自动聚焦选中项

解决方案

针对Android TV Compose中ModalNavigationDrawer打开时自动聚焦选中NavigationDrawerItem的需求,可通过以下步骤实现,同时避免抽屉意外关闭、保持选中项高亮:

核心修改点

  • 正确监听抽屉状态,仅在抽屉打开时触发焦点请求
  • 利用NavigationDrawerItem内置的聚焦能力,不额外添加focusable()
  • 移除干扰焦点逻辑的SideEffect和不稳定的onFocusChanged

完整代码示例

@Composable
fun TestDrawerContent() {
    val drawerState = rememberDrawerState(DrawerValue.Closed)
    val mainContentFocusRequester = remember { FocusRequester() }
    val coroutineScope = rememberCoroutineScope()

    var selectedIndex by remember { mutableIntStateOf(0) }
    val menuItems = listOf("Home", "Movies", "Series", "Settings")

    // 为每个菜单项创建FocusRequester
    val itemFocusRequesters = remember(menuItems) {
        List(menuItems.size) { FocusRequester() }
    }

    val closeDrawerWidth = 80.dp
    val backgroundContentPadding = 10.dp

    // 监听抽屉状态,打开时自动聚焦选中项
    LaunchedEffect(drawerState.currentState) {
        if (drawerState.currentState == DrawerValue.Open) {
            // 延迟确保抽屉布局完成后再请求焦点
            delay(100)
            itemFocusRequesters[selectedIndex].requestFocus()
        }
    }

    ModalNavigationDrawer(
        drawerState = drawerState,
        drawerContent = {
            Column(
                modifier = Modifier
                    .fillMaxHeight()
                    .padding(horizontal = 12.dp)
                    .selectableGroup(), // 确保D-pad导航在菜单项间正常流转
                horizontalAlignment = Alignment.Start
            ) {
                menuItems.forEachIndexed { index, title ->
                    NavigationDrawerItem(
                        modifier = Modifier
                            .height(40.dp)
                            .focusRequester(itemFocusRequesters[index]),
                        selected = selectedIndex == index,
                        leadingContent = {
                            Icon(
                                imageVector = Icons.Default.Accessibility,
                                contentDescription = title
                            )
                        },
                        onClick = {
                            selectedIndex = index
                            coroutineScope.launch {
                                drawerState.close()
                                // 抽屉关闭后将焦点返回主内容区
                                mainContentFocusRequester.requestFocus()
                            }
                        }
                    ) {
                        Text(text = title)
                    }
                }
            }
        }
    ) {
        Box(
            modifier = Modifier
                .padding(closeDrawerWidth + backgroundContentPadding)
                .focusRequester(mainContentFocusRequester)
                .focusable() // 主内容区需要明确标记为可聚焦,确保焦点能返回
        ) {
            Column(
                modifier = Modifier.fillMaxSize(),
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                Text("Selected: ${menuItems[selectedIndex]}", fontSize = 24.sp)
                Row(modifier = Modifier.padding(top = 32.dp)) {
                    repeat(3) {
                        Card(
                            modifier = Modifier.padding(horizontal = 16.dp),
                            onClick = {}
                        ) {
                            Column(
                                modifier = Modifier.padding(16.dp),
                                horizontalAlignment = Alignment.CenterHorizontally
                            ) {
                                Icon(
                                    modifier = Modifier.size(94.dp),
                                    imageVector = Icons.Default.Accessibility,
                                    contentDescription = "Favorite item"
                                )
                                Text("Favorites TBD")
                            }
                        }
                    }
                }
            }
        }
    }
}

关键逻辑说明

  1. 抽屉状态监听:
    使用LaunchedEffect(drawerState.currentState)仅在抽屉状态变化时执行逻辑,当状态为Open时,延迟100ms后请求选中项的焦点——延迟是为了确保抽屉布局完全渲染完成,避免焦点请求无效。

  2. 焦点管理:

    • 主内容区添加.focusable(),确保抽屉关闭后焦点能正确返回
    • 每个NavigationDrawerItem绑定对应的FocusRequester,不额外添加focusable(),保留组件原生的选中高亮效果
  3. D-pad导航优化:
    抽屉内容的Column添加.selectableGroup(),确保D-pad在菜单项间的垂直导航逻辑正常工作

常见问题规避

  • 避免在LaunchedEffect中修改drawerState(除非是主动关闭),防止抽屉意外关闭
  • 不要给NavigationDrawerItem添加额外的focusable(),否则会覆盖组件原生的聚焦状态样式
  • 移除不必要的SideEffect,避免重复触发焦点请求导致的混乱

内容的提问来源于stack exchange,提问作者andreich

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 22:43:15