Hopefully this is an easy question, but anyone know how to animate a composable's position based on alignment, for example from Alignment.Start to Alignment.End ?
Hopefully this is an easy question, but anyone know how to animate a composable's position based on alignment, for example from Alignment.Start to Alignment.End ?
There is not build-in support for this kind of animation, but it is possible to implement it.
If you take a look at Alignment.kt file you can see that values like Start, End are in general implemented based on the concept of bias (this concept is also used in e.g. ConstraintLayout). Bias -1 represents start, and 1 represents end and 0 is center.
val Start: Alignment.Horizontal = BiasAlignment.Horizontal(-1f)
val CenterHorizontally: Alignment.Horizontal = BiasAlignment.Horizontal(0f)
val End: Alignment.Horizontal = BiasAlignment.Horizontal(1f)
Since bias is just a number, we can reduce this problem to simply animating a float value.
Unfortunately the Alignment interface is complicated in terms of inheritance and what it exposes as public values (e.g. you cannot read the horizontal bias from values like Alignment.Start). That makes it difficult to create a generic solution.
But if you care only about let's say Alignment.Horizontal you can create function that animated the horizontal bias and creates BiasAlignment.Horizontal object from it:
@Composable
private fun animateHorizontalAlignmentAsState(
targetBiasValue: Float
): State<BiasAlignment.Horizontal> {
val bias by animateFloatAsState(targetBiasValue)
return derivedStateOf { BiasAlignment.Horizontal(bias) }
}
Here is the usage example, when the horizontal alignment bias is toggled (negated) on every click on a Column
var horizontalBias by remember { mutableStateOf(-1f) }
val alignment by animateHorizontalAlignmentAsState(horizontalBias)
Column(
modifier = Modifier
.fillMaxWidth()
.clickable { horizontalBias *= -1 },
horizontalAlignment = alignment
) {
Text("Test")
}
Note that you cannot pass BiasAlignment.Horizontal to something that expects Alignment.Horizontal and it's not possible to pass it when Alignment is expected. You can either change this method to return BiasAlignment or create another one for this type of alignment with two bias values.
Building on @maciej-ciemięga's excellent answer, here's a version that allows animating any of the two-dimensional Alignment values:
@Composable
fun animateAlignmentAsState(
targetAlignment: Alignment,
): State<Alignment> {
val biased = targetAlignment as BiasAlignment
val horizontal by animateFloatAsState(biased.horizontalBias)
val vertical by animateFloatAsState(biased.verticalBias)
return derivedStateOf { BiasAlignment(horizontal, vertical) }
}
It relies on knowing that these values are all BiasAlignments, but it's nice because then the consumer doesn't need to know about the float values:
val a by animateAlignmentAsState(if (foo) Alignment.TopCenter else Alignment.BottomCenter)