在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
相关产品推荐
相关产品推荐

