Jetpack Compose:滚动时Extended FAB折叠动画失效求助
解决Extended FAB展开/折叠无平滑动画的问题
问题根源分析
- 动画未触发的核心原因:你代码里的
AnimatedVisibility将visible固定为true,这意味着容器始终处于显示状态——你只是在这个容器里直接切换两个完全独立的FAB组件,它们的替换是瞬间完成的,enter/exit动画根本不会被触发。 - 滚动判断逻辑缺陷:仅用
firstVisibleItemScrollOffset == 0判断是否在顶部,当第一个列表项完全滚出屏幕时,这个偏移量依然为0,但此时应该折叠FAB。
解决方案1:使用Material3自带的ExtendedFloatingActionButton(推荐)
Material3的ExtendedFloatingActionButton本身内置了展开/折叠的平滑过渡动画,只需通过extended参数控制状态,还能自定义动画时长和曲线,无需手动切换组件。
package com.example.turnorder.composables.shared import androidx.compose.foundation.lazy.grid.LazyGridState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material3.ExtendedFloatingActionButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier import androidx.compose.animation.core.tween @Composable fun ScrollAwareFAB( listState: LazyGridState, modifier: Modifier = Modifier, onClick: () -> Unit ) { var isExtended by remember { mutableStateOf(true) } // 修正滚动判断:仅当列表处于真正顶部时展开FAB LaunchedEffect(listState) { snapshotFlow { listState.firstVisibleItemIndex == 0 && listState.firstVisibleItemScrollOffset == 0 }.collect { isAtTop -> isExtended = isAtTop } } ExtendedFloatingActionButton( onClick = onClick, icon = { Icons.Filled.Add }, text = { Text("Add Character") }, extended = isExtended, // 自定义展开/折叠动画时长(这里设置为500ms) expandedAnimationSpec = tween(durationMillis = 500), collapsedAnimationSpec = tween(durationMillis = 500), containerColor = MaterialTheme.colorScheme.primary, contentColor = MaterialTheme.colorScheme.onPrimary, modifier = modifier ) }
解决方案2:自定义过渡动画(使用AnimatedContent)
如果需要完全自定义动画效果(比如横向展开/收缩),可以用AnimatedContent替代AnimatedVisibility,它专门用于在两个内容状态之间添加过渡动画。
package com.example.turnorder.composables.shared import androidx.compose.animation.AnimatedContent import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.expandHorizontally import androidx.compose.animation.shrinkHorizontally import androidx.compose.animation.with import androidx.compose.foundation.layout.Box import androidx.compose.foundation.lazy.grid.LazyGridState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material3.ExtendedFloatingActionButton import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.animation.core.tween @OptIn(ExperimentalAnimationApi::class) @Composable fun ScrollAwareFAB( listState: LazyGridState, modifier: Modifier = Modifier, onClick: () -> Unit ) { var isExtended by remember { mutableStateOf(true) } LaunchedEffect(listState) { snapshotFlow { listState.firstVisibleItemIndex == 0 && listState.firstVisibleItemScrollOffset == 0 }.collect { isAtTop -> isExtended = isAtTop } } AnimatedContent( targetState = isExtended, transitionSpec = { if (targetState) { // 展开动画:从右侧横向展开 expandHorizontally( expandFrom = Alignment.End, animationSpec = tween(500) ) with shrinkHorizontally( shrinkTowards = Alignment.End, animationSpec = tween(500) ) } else { // 折叠动画:向右侧横向收缩 shrinkHorizontally( shrinkTowards = Alignment.End, animationSpec = tween(500) ) with expandHorizontally( expandFrom = Alignment.End, animationSpec = tween(500) ) } } ) { state -> Box(modifier = modifier) { if (state) { ExtendedFloatingActionButton( text = { Text("Add Character") }, icon = { Icon(Icons.Filled.Add, contentDescription = "Add") }, onClick = onClick, containerColor = MaterialTheme.colorScheme.primary, contentColor = MaterialTheme.colorScheme.onPrimary ) } else { FloatingActionButton( onClick = onClick, containerColor = MaterialTheme.colorScheme.primary, contentColor = MaterialTheme.colorScheme.onPrimary ) { Icon(Icons.Filled.Add, contentDescription = "Add") } } } } }
内容的提问来源于stack exchange,提问作者Cassie S.
相关产品推荐
相关产品推荐

