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

Compose中ExposedDropdownMenuBox结合ViewModel二次点击无法展开

ExposedDropdownMenuBox二次点击无法展开(ViewModel场景)

问题描述

Jetpack Compose开发中,ExposedDropdownMenuBox结合ViewModel使用时,首次点击可正常展开菜单,但第二次点击无响应;移除ViewModel改用本地remember状态时,代码完全正常。该问题发生在TabLayout布局场景下。

问题代码

Compose代码

@Composable
fun Input(viewModel: InputViewModel = androidx.lifecycle.viewmodel.compose.viewModel()) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .padding(bottom = 8.dp),
        horizontalArrangement = Arrangement.SpaceBetween
    ) {
        ExposedDropdownMenuBox(
            modifier = Modifier
                .fillMaxWidth()
                .padding(bottom = 8.dp)
                .weight(1f),
            expanded = viewModel.expanded,
            onExpandedChange = { viewModel.expanded = it }
        )
        {
            OutlinedTextField(
                label = { Text("Duration") },
                value = viewModel.selectedTermOption.toString(),
                onValueChange = {},
                readOnly = true,
                trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = viewModel.expanded) },
                modifier = Modifier
                    .menuAnchor()
            )
            ExposedDropdownMenu(
                modifier = Modifier
                    .exposedDropdownSize(),
                expanded = viewModel.expanded,
                onDismissRequest = { viewModel.expanded = false }
            ) {
                TermOption.entries.forEach { item ->
                    DropdownMenuItem(
                        text = { Text(text = item.toString()) },
                        modifier = Modifier
                            .fillMaxWidth(),
                        onClick = {
                            viewModel.setTermOption(item)
                            viewModel.expanded = false
                        }
                    )
                }
            }
        }
    }
}

ViewModel代码

class InputViewModel : ViewModel()
{
    var selectedTermOption by mutableStateOf(TermOption.YEARS)
    var expanded by mutableStateOf(false)
    init {
        setTermOption(selectedTermOption)
    }

    fun setTermOption(item: TermOption) {
        selectedTermOption = item
    }
}

解决方案

问题核心是ViewModel中直接暴露mutableStateOf变量并直接赋值,可能导致Compose在TabLayout这类组件复用/重组场景下,无法正确感知状态变化时机。

修复步骤

  1. 封装ViewModel状态:将expanded改为私有可变状态,通过公共只读属性和方法暴露、更新状态,确保状态变更可追踪。
  2. 统一状态更新逻辑:使用方法而非直接赋值修改状态,避免意外的状态覆盖。

修复后的ViewModel代码:

class InputViewModel : ViewModel()
{
    var selectedTermOption by mutableStateOf(TermOption.YEARS)
    private val _expanded = mutableStateOf(false)
    val expanded: Boolean get() = _expanded.value

    init {
        setTermOption(selectedTermOption)
    }

    fun setTermOption(item: TermOption) {
        selectedTermOption = item
    }

    fun updateExpandedState(isExpanded: Boolean) {
        _expanded.value = isExpanded
    }
}

修复后的Compose代码:

@Composable
fun Input(viewModel: InputViewModel = androidx.lifecycle.viewmodel.compose.viewModel()) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .padding(bottom = 8.dp),
        horizontalArrangement = Arrangement.SpaceBetween
    ) {
        ExposedDropdownMenuBox(
            modifier = Modifier
                .fillMaxWidth()
                .padding(bottom = 8.dp)
                .weight(1f),
            expanded = viewModel.expanded,
            onExpandedChange = { viewModel.updateExpandedState(it) }
        )
        {
            OutlinedTextField(
                label = { Text("Duration") },
                value = viewModel.selectedTermOption.toString(),
                onValueChange = {},
                readOnly = true,
                trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = viewModel.expanded) },
                modifier = Modifier
                    .menuAnchor()
            )
            ExposedDropdownMenu(
                modifier = Modifier
                    .exposedDropdownSize(),
                expanded = viewModel.expanded,
                onDismissRequest = { viewModel.updateExpandedState(false) }
            ) {
                TermOption.entries.forEach { item ->
                    DropdownMenuItem(
                        text = { Text(text = item.toString()) },
                        modifier = Modifier
                            .fillMaxWidth(),
                        onClick = {
                            viewModel.setTermOption(item)
                            viewModel.updateExpandedState(false)
                        }
                    )
                }
            }
        }
    }
}

原理说明

通过封装状态并集中管理更新逻辑,确保状态变更的可预测性,避免直接赋值导致的Compose重组时机异常。同时私有可变状态+公共只读属性的方式,符合ViewModel状态封装的最佳实践,防止外部意外修改状态。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 03:10:57