动画

基础动画

// 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 ... }

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容