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

在Jetpack Compose中实现Spotify风格音乐播放条形动画

实现Jetpack Compose版Spotify风格跳动条形动画

需求说明

我正在开发一款查看好友听歌状态的卡片项目,当前使用Jetpack Compose编写的FriendCard组件中,当好友正在听歌时仅显示Material Icons的播放图标。希望将其替换为类似Spotify桌面端的三条形跳动动画,但找到的旧方案不支持Compose,需要实现帮助。

原组件代码:

@Composable
fun FriendCard(friend: User, recentTracks: RecentTracks?) {
    var albumNameWidth by remember { mutableStateOf(200.dp) }
    Card(
        modifier = Modifier
            .padding(8.dp)
            .fillMaxWidth()
    ) {
        Row(
            modifier = Modifier
                .padding(8.dp)
                .fillMaxWidth(),
            verticalAlignment = Alignment.CenterVertically
        ) {
            Column(
                horizontalAlignment = Alignment.Start,
            ) {
                recentTracks?.track?.firstOrNull()?.let { track ->
                    Text(
                        text = track.name,
                        style = MaterialTheme.typography.titleMedium,
                        maxLines = 1,
                        modifier = Modifier
                            .width(750.dp)
                            .basicMarquee()
                    )
                    Row(
                        modifier = Modifier.fillMaxWidth(),
                        horizontalArrangement = Arrangement.SpaceBetween
                    ) {
                        albumNameWidth =
                            if (track.dateInfo?.formattedDate == null) 230.dp else 200.dp
                        Text(
                            text = track.album.name.ifEmpty { stringResource(R.string.unknown_album) },
                            style = MaterialTheme.typography.labelLarge.copy(
                                color = MaterialTheme.colorScheme.onSurface
                            ),
                            maxLines = 1,
                            modifier = Modifier
                                .padding(end = 8.dp)
                                .width(albumNameWidth)
                                .basicMarquee()
                        )
                        if (track.dateInfo?.formattedDate == null) {
                            //if the person is listening to it right now
                              Icon(
                                  imageVector = Icons.Default.PlayArrow,
                                  tint = MaterialTheme.colorScheme.onSurfaceVariant,
                                  contentDescription = stringResource(R.string.now_playing),
                                  modifier = Modifier.align(Alignment.CenterVertically)
                              )
                        } else {
                            Text(
                                text = formatData(track.dateInfo.formattedDate),
                                style = MaterialTheme.typography.labelLarge.copy(
                                    color = MaterialTheme.colorScheme.onSurfaceVariant,
                                    fontSize = 13.sp
                                ),
                                maxLines = 1,
                                modifier = Modifier.align(Alignment.CenterVertically)
                            )
                        }
                    }
                } ?: Text(
                    text = stringResource(id = R.string.no_recent_tracks),
                    style = MaterialTheme.typography.bodySmall.copy(
                        color = MaterialTheme.colorScheme.onSurfaceVariant
                    )
                )
            }
        }
    }
}

解决方案

1. 自定义跳动条形动画组件

创建一个SpotifyPlayingAnimator组件,用Compose动画API实现三条条形的随机高度跳动效果:

@Composable
fun SpotifyPlayingAnimator(
    tint: Color = MaterialTheme.colorScheme.onSurfaceVariant,
    modifier: Modifier = Modifier,
    barWidth: Dp = 4.dp,
    barGap: Dp = 3.dp,
    minBarHeight: Dp = 8.dp,
    maxBarHeight: Dp = 16.dp
) {
    // 为每个条形设置独立动画,不同延迟模拟随机跳动
    val bar1Height by animateFloatAsState(
        targetValue = if (Random.nextBoolean()) 1f else 0.3f,
        animationSpec = infiniteRepeatable(
            animation = tween(600, easing = LinearEasing),
            repeatMode = RepeatMode.Reverse
        ),
        label = "bar1"
    )
    val bar2Height by animateFloatAsState(
        targetValue = if (Random.nextBoolean()) 0.7f else 0.4f,
        animationSpec = infiniteRepeatable(
            animation = tween(700, delayMillis = 100, easing = LinearEasing),
            repeatMode = RepeatMode.Reverse
        ),
        label = "bar2"
    )
    val bar3Height by animateFloatAsState(
        targetValue = if (Random.nextBoolean()) 0.5f else 0.6f,
        animationSpec = infiniteRepeatable(
            animation = tween(500, delayMillis = 200, easing = LinearEasing),
            repeatMode = RepeatMode.Reverse
        ),
        label = "bar3"
    )

    Row(
        modifier = modifier,
        horizontalArrangement = Arrangement.spacedBy(barGap),
        verticalAlignment = Alignment.Bottom
    ) {
        Box(
            modifier = Modifier
                .width(barWidth)
                .height(minBarHeight + (maxBarHeight - minBarHeight) * bar1Height)
                .background(tint, RoundedCornerShape(2.dp))
        )
        Box(
            modifier = Modifier
                .width(barWidth)
                .height(minBarHeight + (maxBarHeight - minBarHeight) * bar2Height)
                .background(tint, RoundedCornerShape(2.dp))
        )
        Box(
            modifier = Modifier
                .width(barWidth)
                .height(minBarHeight + (maxBarHeight - minBarHeight) * bar3Height)
                .background(tint, RoundedCornerShape(2.dp))
        )
    }
}

2. 替换原组件中的播放图标

在FriendCard组件里,把原来显示PlayArrow图标的代码替换成自定义动画组件:

if (track.dateInfo?.formattedDate == null) {
    // 现在播放状态显示跳动条形动画
    SpotifyPlayingAnimator(
        tint = MaterialTheme.colorScheme.onSurfaceVariant,
        modifier = Modifier.align(Alignment.CenterVertically)
    )
} else {
    // 原有历史播放时间显示逻辑保持不变
    Text(
        text = formatData(track.dateInfo.formattedDate),
        style = MaterialTheme.typography.labelLarge.copy(
            color = MaterialTheme.colorScheme.onSurfaceVariant,
            fontSize = 13.sp
        ),
        maxLines = 1,
        modifier = Modifier.align(Alignment.CenterVertically)
    )
}

说明

  • 三个条形用独立的动画状态,不同的延迟和时长模拟Spotify的随机跳动效果
  • 通过infiniteRepeatable实现无限循环,RepeatMode.Reverse让条形在最小/最大高度间来回变化
  • 可通过参数调整条形的尺寸、间距,适配不同UI风格
  • 动画随Compose自动管理生命周期,无需额外处理

内容的提问来源于stack exchange,提问作者Murilo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 16:25:55