Jetpack Compose共享元素过渡异常问题求助
Jetpack Compose 共享元素过渡异常问题
初始状态下,列表页与详情页(新增/编辑页)的共享元素过渡正常,但触发以下操作后过渡效果失效,仅显示元素消失后重新弹出,不再有首次的ResizeMode.ScaleToBounds过渡效果:
- 通过FAB进入新增笔记页
- 搜索笔记
- 快速删除并恢复笔记
怀疑是笔记重组导致的异常,已尝试切换为shareElement modifier并调整其位置,但问题仍未解决。
相关代码
NoteContent 代码
@OptIn(ExperimentalSharedTransitionApi::class) @Composable fun NoteContent( state: NoteState, query: String, onOrderChange: (NoteOrder) -> Unit, onToggleOrderSectionClick: () -> Unit, onDeleteClick: (Note) -> Unit, onUndoClick: () -> Unit, navigateToAddEdit: (Int, Int) -> Unit, onQueryChange: (String) -> Unit, sharedTransitionScope: SharedTransitionScope, animatedVisibilityScope: AnimatedVisibilityScope ) { val snackbarHostState = remember { SnackbarHostState() } val scope = rememberCoroutineScope() val staggeredGridState = rememberLazyStaggeredGridState() with(sharedTransitionScope){ Scaffold( floatingActionButton = { FloatingActionButton( onClick = {navigateToAddEdit(-1,-1)}, containerColor = MaterialTheme.colorScheme.primary, ) { Icon(imageVector = Icons.Default.Add, contentDescription = "Add Note") } }, snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, ) { padding -> Column( modifier = Modifier .fillMaxSize() .padding(horizontal = 8.dp) ) { LazyVerticalStaggeredGrid( state = staggeredGridState, columns = StaggeredGridCells.Fixed(2), modifier = Modifier.fillMaxWidth(), contentPadding = PaddingValues(bottom = 4.dp), horizontalArrangement = Arrangement.spacedBy(4.dp), verticalItemSpacing = 4.dp ) { item(span = StaggeredGridItemSpan.FullLine) { SearchBar( query = query, onQueryChange = onQueryChange, onToggleOrderSectionClick = onToggleOrderSectionClick ) } item(span = StaggeredGridItemSpan.FullLine) { AnimatedVisibility( visible = state.isOrderSectionVisible, enter = fadeIn() + slideInVertically(), exit = fadeOut() + slideOutVertically() ) { OrderRadio(modifier = Modifier .fillMaxWidth() .padding(vertical = 8.dp, horizontal = 8.dp), noteOrder = state.noteOrder, onOrderChange = { order -> onOrderChange(order) }) } } items(state.notes, key = { it.id ?: 0 }) { note -> AnimatedVisibility( visible = state.notes.isNotEmpty(), enter = fadeIn(), exit = fadeOut(), ) { NoteItem(note = note, modifier = Modifier .padding(4.dp) .fillMaxWidth() .animateItem( placementSpec = tween( durationMillis = 300, delayMillis = 0 ) ) .sharedBounds( rememberSharedContentState(key = "note/${note.id}"), animatedVisibilityScope = animatedVisibilityScope, enter = fadeIn(), exit = fadeOut(), resizeMode = SharedTransitionScope.ResizeMode.ScaleToBounds() ), onDeleteClick = { onDeleteClick(note) scope.launch { val result = snackbarHostState.showSnackbar( message = "Note deleted", actionLabel = "Undo" ) if (result == SnackbarResult.ActionPerformed) { onUndoClick() } } }, onClick = { note.id?.let { it1 -> navigateToAddEdit(it1, note.color) } }) } } } AnimatedVisibility( visible = state.notes.isEmpty(), enter = fadeIn(), exit = fadeOut(), ) { EmptyNote( isSearchActive = state.isSearchActive ) } } } } }
AddEditScreen 代码
@OptIn(ExperimentalSharedTransitionApi::class) @Composable fun AddEditNoteScreen( navController: NavController, noteColor : Int, noteId:Int?, viewModel: AddEditViewModel = hiltViewModel(), sharedTransitionScope: SharedTransitionScope, animatedVisibilityScope: AnimatedVisibilityScope ){ val titleState = viewModel.title.value val contentState = viewModel.content.value val snackbarHostState = remember { SnackbarHostState() } Log.d("test", noteColor.toString()) val backgroundAnimateableColor = remember { Animatable( Color(if (noteColor != -1) noteColor else viewModel.color.value) ) } if(noteColor != -1){ viewModel.onEvent(AddEditNoteEvent.ChangeColor(noteColor)) } val scope = rememberCoroutineScope() LaunchedEffect(key1 = true) { viewModel.eventFlow.collectLatest { event -> when (event) { is AddEditViewModel.UiEvent.ShowSnackbar -> { snackbarHostState.showSnackbar( message = event.message ) } is AddEditViewModel.UiEvent.SaveNote -> { navController.navigateUp() } } } } with(sharedTransitionScope){ Scaffold( floatingActionButton = { FloatingActionButton( onClick = { viewModel.onEvent(AddEditNoteEvent.SaveNote) }, containerColor = MaterialTheme.colorScheme.primary ){ Icon(imageVector = Icons.Default.Check , contentDescription = "Save" ) } }, modifier = Modifier .sharedBounds( rememberSharedContentState(key = "note/$noteId"), animatedVisibilityScope = animatedVisibilityScope, enter = fadeIn(), exit = fadeOut(), resizeMode = SharedTransitionScope.ResizeMode.ScaleToBounds() ), snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, ) { paddingValues -> Column( modifier = Modifier .background(backgroundAnimateableColor.value) .fillMaxSize() .padding(20.dp) .padding(paddingValues) ){ Row(modifier = Modifier .fillMaxWidth() .padding(8.dp), horizontalArrangement = Arrangement.SpaceBetween ){ Utils.noteColors.forEach{ color -> val colorInt = color.toArgb() Box( modifier = Modifier .size(50.dp) .shadow(15.dp, CircleShape) .clip(CircleShape) .background(color) .border( width = 3.dp, color = if (backgroundAnimateableColor.value.toArgb() == colorInt) { Color.Black } else Color.Transparent, shape = CircleShape ) .clickable { scope.launch { backgroundAnimateableColor.animateTo( targetValue = Color(colorInt), animationSpec = tween( delayMillis = 300 ) ) } viewModel.onEvent(AddEditNoteEvent.ChangeColor(colorInt)) } ) } } Spacer(modifier = Modifier.height(16.dp)) TrasnparentHintTextField( text = titleState.text, hint = titleState.hint, onValueChange = { viewModel.onEvent(AddEditNoteEvent.EnteredTitle(it)) }, onFocusChange = { viewModel.onEvent(AddEditNoteEvent.IsFocusTitle(it)) }, isHintVisible = titleState.isHintVisible, singleLine = true, textStyle = MaterialTheme.typography.headlineSmall ) Spacer(modifier = Modifier.height(16.dp)) TrasnparentHintTextField( text = contentState.text, hint = contentState.hint, onValueChange = { viewModel.onEvent(AddEditNoteEvent.EnteredContent(it)) }, onFocusChange = { viewModel.onEvent(AddEditNoteEvent.IsFocusContent(it)) }, isHintVisible = contentState.isHintVisible, singleLine = false, textStyle = MaterialTheme.typography.bodyLarge ) } } } }
修复方案
1. 区分新增/编辑场景的共享元素逻辑
新增笔记时没有对应的源元素,不应启用共享元素过渡。修改AddEditScreen的Scaffold modifier:
modifier = if (noteId != -1) { Modifier.sharedBounds( rememberSharedContentState(key = "note/$noteId"), animatedVisibilityScope = animatedVisibilityScope, enter = fadeIn(), exit = fadeOut(), resizeMode = SharedTransitionScope.ResizeMode.ScaleToBounds() ) } else { Modifier },
2. 移除列表项外层干扰性的AnimatedVisibility
列表项的AnimatedVisibility使用全局列表非空作为条件,会批量触发动画干扰共享元素过渡,直接移除该包裹:
// NoteContent中的列表项部分修改为: items(state.notes, key = { it.id ?: 0 }) { note -> NoteItem( note = note, modifier = Modifier .padding(4.dp) .fillMaxWidth() .animateItem(placementSpec = tween(durationMillis = 300)) .sharedBounds( rememberSharedContentState(key = "note/${note.id}"), animatedVisibilityScope = animatedVisibilityScope, enter = fadeIn(), exit = fadeOut(), resizeMode = SharedTransitionScope.ResizeMode.ScaleToBounds() ), onDeleteClick = { /* 原有逻辑不变 */ }, onClick = { /* 原有逻辑不变 */ } ) }
3. 确保共享元素Key的稳定性
- 删除恢复笔记时,保证笔记的
id不被重新生成,维持note/${note.id}的一致性 - 搜索时,确保过滤后的笔记列表中,保留原有的
id,避免Key失效
4. 确认TransitionScope的正确性
确保sharedTransitionScope是在导航宿主(如NavHost所在的Composable)中通过rememberSharedTransitionScope()创建,并传递给所有需要共享过渡的子Composable,避免Scope因重组丢失状态。
内容的提问来源于stack exchange,提问作者agung Dwi
相关产品推荐
相关产品推荐

