我有一个例子,在这种情况下,使用单个动画浮点值来控制其他元素的动画会很有用,包括两种 colored颜色 之间的淡入淡出.通常是否有建议的方法来完成此操作,如声明其当前状态值直接由外部可变浮点状态控制的Animatable?例如,如果可变浮点在特定实例中为0.25,则它控制的所有动画将是一个状态和另一个状态之间的25%.

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

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

如有任何 idea ,我们不胜感激!

推荐答案

Jetpack Compose具有为各种类别定义的线性内插函数lerp,包括RectColorFontSizeSize、偏移量、Shadow以及除FloatIntLong之外的许多类别.

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

implementation "androidx.compose.ui:ui-util:$compose_version"

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

/**
 * 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()
}

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

// 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)

使用这两个函数和一个Animatable或任何animateFloatAsState,您可以用一个值同步多个动作.

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

Linear Interpolation Animation

@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
        )
    }
}

完整的代码可以在这里找到

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

Android相关问答推荐

RippleTheme在作曲material 1.7.0中被废弃

如何在Jetpack composeH中创建具有弯曲末端的六边形形状

Android可绘制边框删除底线

StateFlow和LazyColumn重组

在Jetpack Compose中实现自动换行

在 Compose 中,当用户持续向下滚动时,LazyColumn 不会显示新项目

如何在每次显示可组合项时执行代码(并且只执行一次)

如何在 Jetpack Compose 中对数据类进行 Parcelize

Compose 状态不是 recomposing

围绕动态大小的内容包装 Jetpack Compose Row?

如何让这个三角形指示器在 android jetpack compose 中旋转和移动?

找不到(包名称).在以下位置搜索:

为片段设置主题

Android:appcompat 和 material 如何从默认创建 appcompat 和 material 视图?

如何在屏幕旋转或系统主题更改后将光标移动到 TextField 的末尾并保持键盘显示?

如何使用文件提供程序将视频从一个应用程序共享到另一个应用程序?

当我更改 ViewModel var 时,Kotlin + Compose 中的 Composable 不会更新

Google Play 控制台您的应用是否使用广告 ID?

为什么使用 React Native 和 expo 创建的 APK 体积这么大?

为什么在try 实例化 Mediaplayer 时会出现 NullPointerException?安卓Kotlin