骨架屏与微光 (Shimmer)
在数据加载时,显示一个类似内容布局的灰色占位符,并带有微光扫过的动画效果,这种体验远优于单纯的旋转圆圈。
1. 基础实现原理
微光效果本质上是一个线性渐变 (LinearGradient) 的画笔,在水平方向上不断移动。
kotlin
fun Modifier.shimmer(): Modifier = composed {
val transition = rememberInfiniteTransition()
val translateAnim by transition.animateFloat(
initialValue = 0f,
targetValue = 1000f,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = 1000, easing = FastOutSlowInEasing),
repeatMode = RepeatMode.Restart
)
)
val brush = Brush.linearGradient(
colors = listOf(
Color.LightGray.copy(alpha = 0.6f),
Color.LightGray.copy(alpha = 0.2f),
Color.LightGray.copy(alpha = 0.6f),
),
start = Offset.Zero,
end = Offset(x = translateAnim, y = translateAnim)
)
this.background(brush)
}2. 第三方库 (推荐)
虽然手写不难,但处理复杂的圆角和组合布局时,使用成熟的库更方便。Google 官方并没有提供 Shimmer,社区最流行的是 com.valentinilk.shimmer:compose-shimmer。
依赖
kotlin
implementation("com.valentinilk.shimmer:compose-shimmer:1.2.0")使用
只需在父容器加上 Modifier.shimmer(),子元素就会自动应用效果。
kotlin
import com.valentinilk.shimmer.shimmer
@Composable
fun SkeletonItem() {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.shimmer() // 魔法发生在这里
) {
// 圆形头像占位
Box(
modifier = Modifier
.size(48.dp)
.background(Color.Gray, CircleShape)
)
Spacer(modifier = Modifier.width(8.dp))
Column {
// 标题占位
Box(
modifier = Modifier
.width(100.dp)
.height(20.dp)
.background(Color.Gray, RoundedCornerShape(4.dp))
)
Spacer(modifier = Modifier.height(8.dp))
// 内容占位
Box(
modifier = Modifier
.fillMaxWidth()
.height(14.dp)
.background(Color.Gray, RoundedCornerShape(4.dp))
)
}
}
}