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

如何通过ViewModel与UiState管理LazyColumn单个Dog卡片展开状态

Jetpack Compose 实现单/多卡片独立展开状态的UiState管理

最近学习Jetpack Compose时,用ViewModel+UiState管理状态遇到了问题:想在DogUiState里直接管理每个Dog卡片的展开状态,但点击按钮时所有卡片会同时展开,而非单个。之前在MainActivity里加变量解决了,但不符合状态集中管理的思路。

现有代码问题分析

你当前用expandedDogName存单个ID,只能实现「同时展开一个卡片」的逻辑,但如果曾尝试把isExpanded作为DogUiState的全局布尔变量,所有卡片会绑定同一个状态,自然一点击就全部展开——这就是问题根源。


方案一:给Dog数据类添加展开状态(适合允许修改Dog类的场景)

如果可以修改Dog数据类,直接给它加isExpanded属性,让每个Dog实例自己维护展开状态,UiState只需要管理列表即可。

修改后的代码

1. 更新Dog数据类

data class Dog(
    val name: Int,
    val age: Int,
    val imageResourceId: Int,
    val hobbies: String,
    val isExpanded: Boolean = false // 新增展开状态属性
)

2. 简化DogUiState

data class DogUiState(
    val dogsList: List<Dog> = emptyList()
)

3. 调整ViewModel的状态更新逻辑

class DogViewModel: ViewModel() {
    private val _dogUiState = MutableStateFlow(DogUiState())
    val dogUiState: StateFlow<DogUiState> = _dogUiState.asStateFlow()

    // 更新Dog列表(假设dogs是你的数据源)
    fun updateDogList() {
        _dogUiState.value = DogUiState(dogsList = dogs)
    }

    // 切换指定Dog的展开状态
    fun toggleExpanded(dogName: Int) {
        _dogUiState.update { currentState ->
            val updatedDogs = currentState.dogsList.map { dog ->
                if (dog.name == dogName) {
                    dog.copy(isExpanded = !dog.isExpanded)
                } else {
                    dog
                }
            }
            currentState.copy(dogsList = updatedDogs)
        }
    }
}

4. 优化DogItem Composable

@Composable
fun DogItem(
    dog: Dog,
    modifier: Modifier = Modifier,
    onToggleExpanded: () -> Unit
) {
    Card(modifier = modifier) {
        Column(
            modifier = Modifier
                .animateContentSize(
                    animationSpec = spring(
                        dampingRatio = Spring.DampingRatioNoBouncy,
                        stiffness = Spring.StiffnessMedium
                    )
                )
        ) {
            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(dimensionResource(R.dimen.padding_small))
            ) {
                DogIcon(dog.imageResourceId)
                DogInformation(dog.name, dog.age)
                Spacer(Modifier.weight(1f))
                DogItemButton(
                    expanded = dog.isExpanded,
                    onClick = onToggleExpanded,
                )
            }
            if (dog.isExpanded) {
                DogHobby(
                    dog.hobbies, modifier = Modifier.padding(
                        start = dimensionResource(R.dimen.padding_medium),
                        top = dimensionResource(R.dimen.padding_small),
                        bottom = dimensionResource(R.dimen.padding_medium),
                        end = dimensionResource(R.dimen.padding_medium)
                    )
                )
            }
        }
    }
}

父Composable中调用

val uiState by viewModel.dogUiState.collectAsState()
LazyColumn {
    items(uiState.dogsList) { dog ->
        DogItem(
            dog = dog,
            onToggleExpanded = { viewModel.toggleExpanded(dog.name) }
        )
    }
}

方案二:在UiState里维护展开的ID集合(不修改Dog类的场景)

如果不能修改Dog类,就在DogUiState里用Set存储所有展开的dogName,既支持多卡片同时展开,也能通过修改逻辑实现单卡片展开。

修改后的代码

1. 更新DogUiState

data class DogUiState(
    val dogsList: List<Dog> = emptyList(),
    val expandedDogNames: Set<Int> = emptySet() // 存储所有展开的dogName
)

2. 调整ViewModel的状态更新逻辑

class DogViewModel: ViewModel() {
    private val _dogUiState = MutableStateFlow(DogUiState())
    val dogUiState: StateFlow<DogUiState> = _dogUiState.asStateFlow()

    fun updateDogList() {
        _dogUiState.value = DogUiState(dogsList = dogs)
    }

    fun toggleExpanded(dogName: Int) {
        _dogUiState.update { currentState ->
            val newExpandedSet = if (currentState.expandedDogNames.contains(dogName)) {
                // 已展开则移除
                currentState.expandedDogNames - dogName
            } else {
                // 未展开则添加;如果要限制单卡片展开,替换为 setOf(dogName)
                currentState.expandedDogNames + dogName
            }
            currentState.copy(expandedDogNames = newExpandedSet)
        }
    }
}

3. 优化DogItem Composable

@Composable
fun DogItem(
    dog: Dog,
    modifier: Modifier = Modifier,
    isExpanded: Boolean,
    onToggleExpanded: () -> Unit
) {
    Card(modifier = modifier) {
        Column(
            modifier = Modifier
                .animateContentSize(
                    animationSpec = spring(
                        dampingRatio = Spring.DampingRatioNoBouncy,
                        stiffness = Spring.StiffnessMedium
                    )
                )
        ) {
            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(dimensionResource(R.dimen.padding_small))
            ) {
                DogIcon(dog.imageResourceId)
                DogInformation(dog.name, dog.age)
                Spacer(Modifier.weight(1f))
                DogItemButton(
                    expanded = isExpanded,
                    onClick = onToggleExpanded,
                )
            }
            if (isExpanded) {
                DogHobby(
                    dog.hobbies, modifier = Modifier.padding(
                        start = dimensionResource(R.dimen.padding_medium),
                        top = dimensionResource(R.dimen.padding_small),
                        bottom = dimensionResource(R.dimen.padding_medium),
                        end = dimensionResource(R.dimen.padding_medium)
                    )
                )
            }
        }
    }
}

父Composable中调用

val uiState by viewModel.dogUiState.collectAsState()
LazyColumn {
    items(uiState.dogsList) { dog ->
        DogItem(
            dog = dog,
            isExpanded = uiState.expandedDogNames.contains(dog.name),
            onToggleExpanded = { viewModel.toggleExpanded(dog.name) }
        )
    }
}

内容的提问来源于stack exchange,提问作者Fabio De Cena

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 16:39:55