基础动画
// 1. 定义状态
var isSelected by remember { mutableStateOf(false) }
// 2. 创建动画数值(当 isSelected 改变时,这些值会平滑过渡)
val scale by animateFloatAsState(targetValue = if (isSelected) 2f else 1f)
val rotation by animateFloatAsState(targetValue = if (isSelected) 360f else 0f)
val offsetBy by animateDpAsState(targetValue = if (isSelected) 100.dp else 0.dp)
// 3. 应用到 Modifier
Box(
Modifier
.size(100.dp)
.offset(x = offsetBy) // 平移
.rotate(rotation) // 旋转
.scale(scale) // 缩放
.background(Color.Blue)
.clickable { isSelected = !isSelected }
)
平移 (Translation)
在 Compose 中有两种平移方式:
.offset(x, y): 最常用,直接移动组件位置。
.graphicsLayer { translationX = ... }: 性能更高。因为它是在绘图层处理的,不会触发重新布局(Relayout),适合高频变动的动画。
B. 缩放 (Scale)
.scale(float): 整体缩放。
.graphicsLayer { scaleX = ...; scaleY = ... }: 可以分别控制横向和纵向缩放。
C. 旋转 (Rotation)
.rotate(degrees): 绕中心点旋转。
transformable: 如果需要用户手势控制旋转,通常配合这个 Modifier。
高级控制
val animOffset = remember { Animatable(0f) }
LaunchedEffect(isSelected) {
if (isSelected) {
// 1. 快速弹到 120f
animOffset.animateTo(120f, animationSpec = spring(dampingRatio = Spring.DampingRatioHighBouncy))
// 2. 紧接着平滑回到 100f
animOffset.animateTo(100f, animationSpec = tween(500))
} else {
animOffset.animateTo(0f)
}
}
在做平移、缩放、旋转动画时,请优先使用 graphicsLayer:
Modifier.graphicsLayer {
// 这样做动画,Compose 只会重绘这一小块区域,
// 而不会让整个页面的代码重新跑一遍,性能极佳!
scaleX = scale
scaleY = scale
rotationZ = rotation
translationX = offsetPx
}
旋转重复动画
@Composable
fun BetterLoadingIcon() {
// 创建一个无限循环的转换实例
val transition = rememberInfiniteTransition(label = "infinite")
// 定义循环的数值范围
val rotation by transition.animateFloat(
initialValue = 0f,
targetValue = 360f,
animationSpec = infiniteRepeatable(
animation = tween(1000, easing = LinearEasing),
repeatMode = RepeatMode.Restart
),
label = "rotation"
)
Box(
modifier = Modifier
.size(50.dp)
.graphicsLayer { rotationZ = rotation } // 使用 graphicsLayer 性能更优
.background(Color.LightGray, shape = CircleShape),
contentAlignment = Alignment.Center
) {
// 画一个简单的进度缺口
Canvas(modifier = Modifier.fillMaxSize()) {
drawArc(
color = Color.Blue,
startAngle = -90f,
sweepAngle = 90f,
useCenter = false,
style = Stroke(width = 8f, cap = StrokeCap.Round)
)
}
}
}
精细控制
1.使用 keyframes (最简单、最推荐)
val animScale by animateFloatAsState(
targetValue = if (start) 2f else 1f,
animationSpec = keyframes {
durationMillis = 3000 // 总时长 3s
1f at 0 // 0s 时保持 1f
1f at 2000 // 重点:第 2s 之前一直保持 1f (实现延迟效果)
2f at 3000 // 第 2s 到 第 3s 之间完成缩放
}
)
val animOffset by animateDpAsState(
targetValue = if (start) 100.dp else 0.dp,
animationSpec = tween(3000) // 平移匀速跑满 3s
)
2.使用 LaunchedEffect + Animatable
val offset = remember { Animatable(0f) }
val scale = remember { Animatable(1f) }
LaunchedEffect(start) {
if (start) {
// 开启两个并发任务
launch {
// 平移 3s
offset.animateTo(300f, animationSpec = tween(3000))
}
launch {
delay(2000) // 核心:手动等待 2s
// 执行缩放 1s
scale.animateTo(2f, animationSpec = tween(1000))
}
}
}
// 使用时:Modifier.graphicsLayer { translationX = offset.value; scaleX = scale.value ... }