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

Jetpack Compose:滚动时Extended FAB折叠动画失效求助

解决Extended FAB展开/折叠无平滑动画的问题

问题根源分析

  1. 动画未触发的核心原因:你代码里的AnimatedVisibility将visible固定为true,这意味着容器始终处于显示状态——你只是在这个容器里直接切换两个完全独立的FAB组件,它们的替换是瞬间完成的,enter/exit动画根本不会被触发。
  2. 滚动判断逻辑缺陷:仅用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.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 05:35:35