如何通过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
相关产品推荐
相关产品推荐

