🎯 animateValueAsState 详解

Compose 动画实战

⚡ animateValueAsState 是 Compose 中面向 任意自定义类型 的动画驱动 API。 基于 TwoWayConverter 将任意类型与 AnimationVector 互转,实现 Int、Float、Offset 乃至自定义类型的平滑过渡。

📌 1. animateValueAsState 核心
🔧 函数签名
@Composable
fun <T> animateValueAsState(
    targetValue: T,
    typeConverter: TwoWayConverter<T, AnimationVector>,
    animationSpec: AnimationSpec<T> = tween(),
    label: String = "",
    finishedListener: ((T) -> Unit)? = null
): State<T>
  • targetValue — 目标值,驱动动画
  • typeConverter — 自定义转换器,桥接类型
  • animationSpec — 动画规格 (弹簧/补间等)
  • 返回 State<T>,重组时自动更新
🧩 TwoWayConverter

将任意类型 T 映射为 AnimationVector (1~4维向量)

val FloatToVector: TwoWayConverter<Float, AnimationVector1D> =
    TwoWayConverter(
        convertToVector = { AnimationVector1D(it) },
        convertFromVector = { it.value }
    )

系统内置 Int.VectorConverter, Float.VectorConverter, Offset.VectorConverter 等。

🧪 2. 自定义类型动画
🎨 自定义数据类 · 颜色渐变

假设我们有一个 BoxSize 包含宽、高、圆角,需要同时动画:

data class BoxSize(val width: Float, val height: Float, val radius: Float)

val BoxSizeConverter = TwoWayConverter<BoxSize, AnimationVector3D>(
    convertToVector = { size ->
        AnimationVector3D(size.width, size.height, size.radius)
    },
    convertFromVector = { vector ->
        BoxSize(vector.v1, vector.v2, vector.v3)
    }
)

// 使用
var big by remember { mutableStateOf(false) }
val size by animateValueAsState(
    targetValue = if (big) BoxSize(200f, 200f, 40f) 
                  else BoxSize(100f, 100f, 12f),
    typeConverter = BoxSizeConverter,
    animationSpec = tween(600, easing = FastOutSlowInEasing)
)

✅ 任意维度数据 (1~4) 均可通过 AnimationVector1D~4D 实现平滑过渡。

📊 3. Int · Float · Offset 动画对比
类型转换器 (内置)动画规格示例典型场景
Int Int.VectorConverter tween(500) / spring() 进度值、页码、徽标计数
Float Float.VectorConverter tween(800, easing = LinearEasing) 透明度、旋转角度、缩放
Offset Offset.VectorConverter spring(dampingRatio = 0.5f) 位置移动、拖拽偏移、滑动
🔢 Int 动画
var count by remember { mutableStateOf(0) }
val animatedCount by animateValueAsState(
    targetValue = count,
    typeConverter = Int.VectorConverter,
    animationSpec = tween(400)
)
// Text(text = animatedCount.value.toString())
🥏 Float 动画
var alpha by remember { mutableStateOf(1f) }
val animatedAlpha by animateValueAsState(
    targetValue = alpha,
    typeConverter = Float.VectorConverter,
    animationSpec = spring(
        dampingRatio = 0.3f,
        stiffness = 200f
    )
)
// Box(modifier = Modifier.alpha(animatedAlpha.value))
📍 Offset 动画 (位置移动)
var offset by remember { mutableStateOf(Offset.Zero) }
val animatedOffset by animateValueAsState(
    targetValue = offset,
    typeConverter = Offset.VectorConverter,
    animationSpec = tween(600, easing = FastOutSlowInEasing)
)
// Box(modifier = Modifier.offset { animatedOffset.value.toIntOffset() })

💡 Offset.VectorConverter 自动将 Offset 映射为 AnimationVector2D (x, y)。

⚙️ 动画数据流 · SVG 架构
targetValue TwoWayConverter T ↔ AnimationVector AnimationSpec tween / spring / keyframes State<T> 🔄 每一帧通过 converter 转换 → 插值 → 重组
📋 4. 实用技巧 & 注意事项
🧩 完整组合示例 (Int + Float + Offset)
@Composable
fun MultiValueAnimation() {
    var enabled by remember { mutableStateOf(false) }
    val intValue by animateValueAsState(
        targetValue = if (enabled) 100 else 0,
        typeConverter = Int.VectorConverter,
        animationSpec = tween(600)
    )
    val floatValue by animateValueAsState(
        targetValue = if (enabled) 1f else 0.3f,
        typeConverter = Float.VectorConverter,
        animationSpec = spring(dampingRatio = 0.5f)
    )
    var offsetTarget by remember { mutableStateOf(Offset.Zero) }
    val offsetValue by animateValueAsState(
        targetValue = offsetTarget,
        typeConverter = Offset.VectorConverter,
        animationSpec = tween(400)
    )
    // 组合使用 intValue, floatValue, offsetValue 驱动UI
    Box(
        modifier = Modifier
            .offset { offsetValue.value.toIntOffset() }
            .alpha(floatValue.value)
            .size(intValue.value.dp)
            .background(Color(0xFFF7B731))
    )
}