Jetpack compose - 如何使用值动画直接控制其他动画

Cor*_*sby 3 android android-jetpack-compose

我有一个例子,使用单个动画浮点值来控制其他元素的动画(包括两种颜色之间的淡入淡出)会很有用。一般是否有建议的方法来执行此操作,例如声明一个其当前状态值直接由外部可变浮动状态控制的动画?例如,如果可变浮点数在特定实例中为 0.25,则它控制的所有动画都将是一个状态与另一个状态之间的 25%。

我想要这种行为的原因是强制多个动画完全同步,即使在离开和重新进入合成时也是如此。我知道过渡通常用于控制多个动画,但据我了解,这并不能确保所有子动画完美同步,即以完全相同的完成百分比。

通过使用单个可动画浮动值并使用该值直接设置 UI 元素的位置或颜色,应该可以通过暴力来实现此目的。这是最好的解决方案吗?如果我使用这种方法,我仍然需要计算两种颜色之间的插值,并且我不完全确定如何执行此操作。我尝试深入研究 Compose 源代码,以了解 animateColorAsState() 可组合项是如何完成此操作的。颜色似乎被转换为 4D 矢量,我想它们是从那里线性插值的,但我找不到执行此操作的确切代码。是否有内置函数可以在颜色或矢量之间进行插值?否则,我可以自己计算该值,但我想尝试找到一种更干净的方法来实现所有这一切。

任何想法表示赞赏!

Thr*_*ian 6

Jetpack compose 为各种类定义了线性插值函数 ,lerp包括RectColorFontSizeSize、 Offset以及除、和Shadow之外的许多类。FloatIntLong

对于最后三个,您需要添加

implementation "androidx.compose.ui:ui-util:$compose_version"
Run Code Online (Sandbox Code Playgroud)

或者您可以将它们复制粘贴为

/**
 * Linearly interpolate between [start] and [stop] with [fraction] fraction between them.
 */
fun lerp(start: Float, stop: Float, fraction: Float): Float {
    return (1 - fraction) * start + fraction * stop
}

/**
 * Linearly interpolate between [start] and [stop] with [fraction] fraction between them.
 */
fun lerp(start: Int, stop: Int, fraction: Float): Int {
    return start + ((stop - start) * fraction.toDouble()).roundToInt()
}

/**
 * Linearly interpolate between [start] and [stop] with [fraction] fraction between them.
 */
fun lerp(start: Long, stop: Long, fraction: Float): Long {
    return start + ((stop - start) * fraction.toDouble()).roundToLong()
}
Run Code Online (Sandbox Code Playgroud)

除了线性插值之外,有时还可以使用缩放函数将范围从 0f、1f 更改为您想要的任何范围,可以定义为

// Scale x1 from a1..b1 range to a2..b2 range
private fun scale(a1: Float, b1: Float, x1: Float, a2: Float, b2: Float) =
    androidx.compose.ui.util.lerp(a2, b2, calcFraction(a1, b1, x1))


// Calculate the 0..1 fraction that `pos` value represents between `a` and `b`
private fun calcFraction(a: Float, b: Float, pos: Float) =
    (if (b - a == 0f) 0f else (pos - a) / (b - a)).coerceIn(0f, 1f)
Run Code Online (Sandbox Code Playgroud)

使用这 2 个函数并结合其中一个Animatable或任意一个,animateFloatAsState您可以将多个动画与一个值同步。

在下面的示例中,lerp 和scale 用于更改卡片的矩形位置、文本大小、颜色和偏移量。

线性插值动画

@Composable
fun SnackCard(
    modifier: Modifier = Modifier,
    snack: Snack,
    progress: Float = 0f,
    textColor: Color,
    onClick: () -> Unit
) {

    Box(
        modifier = modifier
            //  Interpolate corner radius
            .clip(RoundedCornerShape(lerp(20.dp, 0.dp, progress)))
            .background(Color.White)
            .clickable(
                onClick = onClick,
                interactionSource = remember { MutableInteractionSource() },
                indication = null
            ),
        contentAlignment = Alignment.TopEnd
    ) {


        //  This is lerping between .6f and 1f by changing start from 0f to .6f
        val fraction = scale(0f, 1f, progress, .6f, 1f)

        Image(
            contentScale = ContentScale.Crop,
            modifier = Modifier.fillMaxWidth()
                .fillMaxHeight(fraction),
            painter = rememberAsyncImagePainter(
                ImageRequest.Builder(LocalContext.current).data(data = snack.imageUrl)
                    .apply(block = fun ImageRequest.Builder.() {
                        crossfade(true)
                        placeholder(drawableResId = R.drawable.placeholder)
                    }).build()
            ),
            contentDescription = null
        )

        Column(
            modifier = Modifier
                .padding(16.dp)
                .align(Alignment.BottomStart)

        ) {
            Text(
                //  Interpolate Font size
                fontSize = lerp(18.sp, 40.sp, progress),
                //  Interpolate Color
                color = lerp(textColor, Color.Black, progress),
                fontWeight = FontWeight.Bold,
                text = snack.name
            )
            CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
                Text(
                    //  Interpolate Font size
                    fontSize = lerp(12.sp, 24.sp, progress),
                    //  Interpolate Color
                    color = lerp(textColor, Color.Black, progress),
                    text = "$${snack.price}"
                )
            }
        }

        FavoriteButton(
            modifier = Modifier.graphicsLayer {
                alpha = 1 - progress
            }
                .padding(12.dp),
            color = textColor
        )
    }
}
Run Code Online (Sandbox Code Playgroud)

完整代码可以在这里找到

https://github.com/SmartToolFactory/Jetpack-Compose-Tutorials/blob/master/Tutorial1-1Basics/src/main/java/com/smarttoolfactory/tutorial1_1basics/chapter6_graphics/Tutorial6_30LinearInterpolation.kt