Reputation: 24662
How can I transition elements of a list to the new list (possibly different size) with animation?
I have a pie chart and when its slices (fractions) change, I want to animate the previous fractions to the new fractions. The thing is, number of slices can be different each time.
If number of new slices is less than the current ones, the current extra slices should animate from their current fraction to 0
.
If number of new slices is greater than the current ones, new extra slices should animate from 0
to their fractions.
@Composable fun PieChartCompose(slices: List<Float>) {
val transitionData = updateTransitionData(slices)
val fractions = transitionData.fractions
// Draw the pie with Canvas using the fractions
}
I have currently implemented this with a list of constant size (10, so slices cannot be more than 10)
(note that the initial animation for chart appearance can be different from subsequent animations):
data class TransitionData(val slices: List<State<Float>>)
enum class ChartState { INITIALIZED, CHANGED }
@Composable fun updateTransitionData(
targetFractions: List<Float>
): TransitionData {
val mutableState = remember { MutableTransitionState(ChartState.INITIALIZED) }
mutableState.targetState = ChartState.CHANGED
val transition = updateTransition(mutableState, label = "main-animation")
val fractions = listOf(
transition.animateFloat(label = "fraction-0-animation") {
if (it == ChartState.INITIALIZED) 0f
else targetSlices.getOrNull(0)?.fraction ?: 0f
},
// ...
transition.animateFloat(label = "fraction-10-animation") {
if (it == ChartState.INITIALIZED) 0f
else targetSlices.getOrNull(10)?.fraction ?: 0f
}
)
return remember(transition) { TransitionData(fractions) }
}
Below is an example chart that initially has two slices and then animates to one slice
(the first slice animates to the single new fraction and the second slice animates to 0
—
they are a little inconsistent probably because of interpolations and animation specs):
var slices by mutableStateOf(listOf(0.3f, 0.7f))
PieChartCompose(slices)
slices = listOf(1f)
Upvotes: 3
Views: 2333
Reputation: 10713
You can try having a dynamic amount of animateFloat
.
Since we want to animate fractions that disappeared, we need to know the old fractions list (in case it's bigger than new one).
That's why I've changed the transition state to operate on fractions list. We can access the "old" state and find the "max" size (comparing old and new fractions list sizes).
The initial state is an empty list, so initially there will be animation from zero for the first fractions.
In animateFloat
we try to take the fraction from the target state
and if the fraction at that position is no longer there - then make it zero, so it will disappear.
I've also added remember(values) { }
around updating values in animatedFractions
which is not needed to work, but it's there rather for optimisation. If the count of values
would not change then all existing objects would be reused and values
list should be the same - then we do not need to update animatedFractions
with new State
objects.
From updateTransitionData
a stable object is returned, with stable list inside. We only modify objects inside of that list. Because it's a SnapshotStateList
it will take care of refreshing all Composables
that iterate over it.
@Composable
fun updateTransitionData(
targetFractions: List<Float>
): TransitionData {
val mutableState = remember { MutableTransitionState(emptyList<Float>()) }
mutableState.targetState = targetFractions
val transition = updateTransition(mutableState, label = "main-animation")
val maxFractionsSize = max(transition.currentState.size, targetFractions.size)
val values = (0 until maxFractionsSize).map { index ->
transition.animateFloat(label = "fraction-$index-animation") { state ->
state.getOrNull(index) ?: 0f
}
}
val animatedFractions = remember(transition) { SnapshotStateList<State<Float>>() }
remember(values) {
animatedFractions.clear()
animatedFractions.addAll(values)
}
return remember(transition) { TransitionData(animatedFractions) }
}
Here is a quick "linear" demo, with slowed down animations, going through 4 different fractions lists:
Upvotes: 4