在官方文档中得知,Compose 1.7.0-beta01 提供共享元素支持,并且 处于实验阶段,这些 API 将来可能会发生变化。但并不是所有人的版本都与之适配,于是在这里我将实现一个差不多效果的过渡动画。
实现思路:
首先定义数据模型和数据来源,这里我给几个假数据
object people{
val people = listOf(
StudyRoom(
imageRes = R.drawable.room2,
name = "共享自习室",
description = "人数:520"
),
StudyRoom(
imageRes = R.drawable.room1,
name = "备考1",
description = "人数:120"
),
StudyRoom(
imageRes = R.drawable.room3,
name = "备考2",
description = "人数:200"
)
)
}
data class StudyRoom(
val imageRes: Int,
val name: String,
val description: String
)
第二步,定义普通卡片和详细卡片内容(也可以根据我的思路自行更改为screen与screen之间的转换)
@Composable
fun PersonCard(
person: StudyRoom,
onClick: () -> Unit,
isVisible: Boolean,
modifier: Modifier = Modifier
) {
SharedTransitionLayout(isVisible = isVisible, modifier = modifier) {
Card(
modifier = Modifier
.width(300.dp)
.height(180.dp)
.clickable { onClick() },
elevation = CardDefaults.cardElevation(defaultElevation = 0.dp),
colors = CardDefaults.cardColors(containerColor = Color.Transparent)
) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(180.dp)
) {
// 背景图片
Image(
painter = painterResource(id = person.imageRes),
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
.height(200.dp),
contentScale = ContentScale.Crop
)
// 叠加层:标题和人数
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.Bottom,
horizontalAlignment = Alignment.Start
) {
Text(
text = person.name,
color = Color.White,
fontSize = 18.sp,
style = typography.titleMedium,
modifier = Modifier
.background(Color(0x80000000)) // 半透明
.padding(4.dp)
)
Text(
text = person.description,
color = Color.White,
fontSize = 14.sp,
style = typography.bodySmall,
modifier = Modifier
.background(Color(0x80000000)) // 半透明
.padding(4.dp)
)
}
}
}
}
}
@Composable
fun PersonDetailsCard(
person: StudyRoom,
isVisible: Boolean,
onClose: () -> Unit
) {
SharedTransitionLayout(isVisible = isVisible) {
Card(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
.clickable { onClose() },
elevation = CardDefaults.cardElevation(4.dp),
colors = CardDefaults.cardColors(containerColor = Color.White)
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Image(
painter = painterResource(id = person.imageRes),
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
.height(200.dp),
contentScale = ContentScale.Crop
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = person.name,
color = Color.Black,
fontSize = 24.sp,
modifier = Modifier.padding(bottom = 4.dp),
style = typography.titleMedium
)
Text(
text = person.description,
color = Color.Gray,
fontSize = 16.sp,
style = typography.bodySmall
)
}
}
}
}
第三步,自定义动画布局,用于实现显示与隐藏的过渡动画
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun SharedTransitionLayout(
isVisible: Boolean,
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
val animationDuration = 600
AnimatedVisibility(
visible = isVisible,
enter = fadeIn(animationSpec = tween(durationMillis = animationDuration, easing = EaseInOutEasing)) +
expandVertically(animationSpec = tween(durationMillis = animationDuration, easing = EaseInOutEasing)),
exit = fadeOut(animationSpec = tween(durationMillis = animationDuration, easing = EaseInOutEasing)) +
shrinkVertically(animationSpec = tween(durationMillis = animationDuration, easing = EaseInOutEasing)),
modifier = modifier
) {
Box(
modifier = Modifier.animateContentSize(
animationSpec = tween(
durationMillis = animationDuration,
easing = EaseInOutEasing
)
)
) {
content()
}
}
}
注意:不要漏了全局变量,用于控制动画的缓动效果。
private val EaseInOutEasing = CubicBezierEasing(0.42f, 0f, 0.58f, 1f)
项目地址:
simonniex/SharedAnimation: compose低版本仿官方共享元素过渡动画的实现 (github.com)