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

Jetpack Compose Bottom Sheet Drawer自动打开问题修复咨询

问题描述

我有如下Compose组件:

@Composable
fun ProductDetails(viewModel: ProductDetailsViewModel) {
    val productInstalmentsByOcrName by viewModel.productInstalmentsByOcrName.collectAsStateWithLifecycle()
    val product by viewModel.product.collectAsStateWithLifecycle()

    val scope = rememberCoroutineScope()
    val drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)

    BottomDrawer(
        drawerState = drawerState,
        drawerContent = {
            ReportModalContent(ocrProductNames = productInstalmentsByOcrName?.map { it.first }
                ?: emptyList(), onReport = { ocrProductName ->
                viewModel.onReport(ocrProductName)
                scope.launch {
                    drawerState.close()
                }
            })
        },
    ) {

// .... 其余代码...

          IconButton(
                    onClick = { scope.launch { drawerState.open() } },
                    modifier = Modifier.align(Alignment.TopEnd)
                ) {
// ... 其余代码

}

该组件对应应用内一个页面,导航进入时首次渲染抽屉处于关闭状态(drawerState.isOpen == false),但随即无需用户点击打开按钮,组件重绘后抽屉自动打开。

代码中未在其他位置操作drawerState,曾怀疑与Navigation组件有关,但设为初始页面仍出现该问题;排查过其他页面相同BottomDrawer的干扰,注释相关代码后问题依旧。

附导航相关代码:

文件 NavigationComposable.kt

fun Navigation(viewModel: NavigationViewModel = koinViewModel()) {
    val navController = rememberNavController()
    val startDestination by viewModel.startRoute.collectAsStateWithLifecycle()
    NavHost(navController = navController, startDestination = startDestination) {
        composable(Routes.AUTH) {
            AuthScreen(navController = navController)
        }
        composable(Routes.PRODUCT_LIST) {
            ProductList(navController = navController)
        }
        composable(
            Routes.PRODUCT_DETAILS,
            arguments = listOf(navArgument(Routes.PRODUCT_ID_ARG) { type = NavType.IntType })
        )
        {
            val productId = it.arguments?.getInt(Routes.PRODUCT_ID_ARG)
            ProductDetails(viewModel = getViewModel { parametersOf(productId) })
        }
    }
}

文件 NavigationViewModel.kt

class NavigationViewModel(authRepository: AuthRepository) : ViewModel() {
    val startRoute =authRepository.isLoggedIn.map {
        if (it) Routes.PRODUCT_LIST else Routes.AUTH
    }.stateIn(viewModelScope, SharingStarted.Eagerly, Routes.AUTH)
}
修复方案

1. 改用rememberSaveable保存抽屉状态

当前drawerState使用remember创建,组件重组时可能出现状态意外重置或错误恢复。使用rememberSaveable搭配BottomDrawerState.Saver,能确保状态在配置变更、组件重组时稳定保留:

val drawerState = rememberSaveable(saver = BottomDrawerState.Saver) {
    BottomDrawerState(BottomDrawerValue.Closed)
}

2. 过滤流的重复数据

检查productInstalmentsByOcrName和product的状态流,若频繁发送重复值会导致组件不必要的重组,间接触发抽屉状态异常。在ViewModel中给流添加distinctUntilChanged()过滤重复数据:

// 在ProductDetailsViewModel中修改对应流
val productInstalmentsByOcrName = yourSourceFlow.distinctUntilChanged()
val product = anotherSourceFlow.distinctUntilChanged()

3. 初始化时强制关闭抽屉

通过LaunchedEffect在组件首次渲染后强制关闭抽屉,确保初始状态稳定:

LaunchedEffect(Unit) {
    drawerState.close()
}

4. 检查子组件的焦点请求

排查ReportModalContent内部是否有自动获取焦点的控件(如输入框),这类控件的requestFocus()操作可能间接触发抽屉打开。移除或禁用子组件的自动焦点逻辑。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 07:57:35