如何在 Jetpack Compose 中制作从 Alignment.Start 到 Align.End 的动画?

Gak*_*ak2 11 android android-animation android-jetpack android-jetpack-compose

希望这是一个简单的问题,但是任何人都知道如何根据对齐方式对可组合项的位置进行动画处理,例如从 Alignment.Start 到 Alignment.End ?

Mac*_*ęga 25

没有对这种动画的内置支持,但可以实现它。

如果您查看Alignment.kt文件,您可以看到像Start,这样的值End通常是基于偏差概念实现的(这个概念也用于例如ConstraintLayout)。Bias -1代表开始1代表结束0代表中心

val Start: Alignment.Horizontal = BiasAlignment.Horizontal(-1f)
val CenterHorizontally: Alignment.Horizontal = BiasAlignment.Horizontal(0f)
val End: Alignment.Horizontal = BiasAlignment.Horizontal(1f)
Run Code Online (Sandbox Code Playgroud)

由于偏差只是一个数字,因此我们可以将这个问题简化为简单地对浮点值进行动画处理。

不幸的是,该Alignment接口在继承及其公开的公共值方面很复杂(例如,您无法从诸如 之类的值中读取水平偏差Alignment.Start)。这使得创建通用解决方案变得困难。

但是,如果您只关心,假设Alignment.Horizontal您可以创建对水平偏差进行动画处理并BiasAlignment.Horizontal从中创建对象的函数:

@Composable
private fun animateHorizontalAlignmentAsState(
    targetBiasValue: Float
): State<BiasAlignment.Horizontal> {
    val bias by animateFloatAsState(targetBiasValue)
    return derivedStateOf { BiasAlignment.Horizontal(bias) }
}
Run Code Online (Sandbox Code Playgroud)

这是使用示例,每次单击时都会切换(取消)水平对齐偏差Column

var horizontalBias by remember { mutableStateOf(-1f) }
val alignment by animateHorizontalAlignmentAsState(horizontalBias)

Column(
    modifier = Modifier
        .fillMaxWidth()
        .clickable { horizontalBias *= -1 },
    horizontalAlignment = alignment
) {
    Text("Test")
}
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

请注意,您无法传递BiasAlignment.Horizontal给期望的东西Alignment.Horizontal,并且不可能在Alignment期望时传递它。您可以更改此方法以返回BiasAlignment或为这种具有两个偏差值的对齐类型创建另一个方法。


Eri*_*ric 9

基于 @maciej-ciemi\xc4\x99ga 的出色答案,这里有一个允许对任何二维Alignment值进行动画处理的版本:

\n
@Composable\nfun animateAlignmentAsState(\n    targetAlignment: Alignment,\n): State<Alignment> {\n    val biased = targetAlignment as BiasAlignment\n    val horizontal by animateFloatAsState(biased.horizontalBias)\n    val vertical by animateFloatAsState(biased.verticalBias)\n    return derivedStateOf { BiasAlignment(horizontal, vertical) }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

它依赖于知道这些值都是BiasAlignments,但这很好,因为这样消费者就不需要知道浮点值:

\n
val a by animateAlignmentAsState(if (foo) Alignment.TopCenter else Alignment.BottomCenter)\n
Run Code Online (Sandbox Code Playgroud)\n


Thr*_*ian 9

您可以在任何AlignmentusingModifier.onPlacedModifier.offsetwith之间设置动画Animatable,如官方文档中所示。

结果

在此输入图像描述

动画修改器

fun Modifier.animatePlacement(): Modifier = composed {
    val scope = rememberCoroutineScope()
    var targetOffset by remember { mutableStateOf(IntOffset.Zero) }
    var animatable by remember {
        mutableStateOf<Animatable<IntOffset, AnimationVector2D>?>(null)
    }
    this
        //  onPlaced should be before offset Modifier
        .onPlaced {
            // Calculate the position in the parent layout
            targetOffset = it
                .positionInParent()
                .round()
        }
        .offset {
            // Animate to the new target offset when alignment changes.
            val anim = animatable ?: Animatable(targetOffset, IntOffset.VectorConverter)
                .also {
                    animatable = it
                }


            if (anim.targetValue != targetOffset) {
                scope.launch {
                    anim.animateTo(targetOffset, spring(stiffness = Spring.StiffnessMediumLow))
                }
            }
            // Offset the child in the opposite direction to the targetOffset, and slowly catch
            // up to zero offset via an animation to achieve an overall animated movement.
            animatable?.let { it.value - targetOffset } ?: IntOffset.Zero
        }
}
Run Code Online (Sandbox Code Playgroud)

可组合示例

@Composable
fun AnimatedChildAlignment(alignment: Alignment) {
    Box(
        Modifier
            .fillMaxSize()
            .padding(4.dp)
            .border(2.dp, Green400)
    ) {
        Box(
            modifier = Modifier
                .animatePlacement()
                .align(alignment)
                .size(100.dp)
                .background(Red400)
        )
    }
}
Run Code Online (Sandbox Code Playgroud)

演示

@Preview
@Composable
fun AnimatedChildAlignmentTest() {
    var alignment by remember {
        mutableStateOf(Alignment.Center)
    }

    var alignmentValue by remember {
        mutableStateOf(0f)
    }

    alignment = when (alignmentValue.roundToInt()) {
        0 -> Alignment.TopStart
        1 -> Alignment.TopCenter
        2 -> Alignment.TopEnd
        3 -> Alignment.CenterStart
        4 -> Alignment.Center
        5 -> Alignment.CenterEnd
        6 -> Alignment.BottomStart
        7 -> Alignment.BottomCenter
        else -> Alignment.BottomEnd
    }

    val text = when (alignmentValue.roundToInt()) {
        0 -> "Alignment.TopStart"
        1 -> "Alignment.TopCenter"
        2 -> "Alignment.TopEnd"
        3 -> "Alignment.CenterStart"
        4 -> "Alignment.Center"
        5 -> "Alignment.CenterEnd"
        6 -> "Alignment.BottomStart"
        7 -> "Alignment.BottomCenter"
        else -> "Alignment.BottomEnd"
    }

    Column(
        modifier = Modifier
            .fillMaxWidth()
            .aspectRatio(1f)
            .padding(10.dp)
    ) {
        
        Spacer(modifier = Modifier.height(20.dp))
        Text(text = "Alignment: $text", fontSize = 20.sp)
        Slider(
            value = alignmentValue,
            onValueChange = {
                alignmentValue = it
            },
            valueRange = 0f..8f,
            steps = 7
        )
        AnimatedChildAlignment(alignment = alignment)
    }
}
Run Code Online (Sandbox Code Playgroud)