I use AnimatedVisibility
to animate deleting an item from a LazyColumn
together with MutableTransitionState
to catch the end of animation to delete this item from LazyColumn
's list.
I composed a handy extension function for this:
@ExperimentalTransitionApi
fun MutableTransitionState<Boolean>.transitionState(): TransitionState =
if (this.isIdle && this.currentState) TransitionState.Visible
else if (this.isIdle && !this.currentState) TransitionState.Invisible
else if (!this.isIdle && this.currentState) TransitionState.Disappearing
else TransitionState.Appearing
enum class TransitionState(private val whatever: Int) {
Visible(1),
Appearing(2),
Invisible(-1),
Disappearing(-2)
}
It is correct in a sense that it returns correct values (tested), but currentState
appears to be false
only initially, so I can't catch the only event I'm interested in - Invisible
.
Here is my LazyColumn
:
val items by viewModel.itemsFlow.collectAsState()
LazyColumn(
verticalArrangement = Arrangement.spacedBy(10.dp),
contentPadding = PaddingValues(horizontal = 15.dp, vertical = 15.dp)
) {
items(items) { item ->
val animationState = remember {
MutableTransitionState(false) // The only time currentState is false!
}.apply { targetState = true }
AnimatedVisibility(
visibleState = animationState,
enter = slideInVertically() fadeIn(),
exit = slideOutHorizontally(
targetOffsetX = { it*2 },
animationSpec = tween(
durationMillis = 700
)
)
) {
ItemCard(
item = item,
viewModel = viewModel,
animationState = animationState
)
}
}
}
My ItemCard
has a Button
that changes animationState.tagetValue
to false
, and the state is logged inside the card:
Card {
Log.v("ANIMATION", "View ${item.name} is now ${animationState.transitionState().name}")
Log.v("ANIMATION", "View ${item.name} has values: isIdle = ${animationState.isIdle}, currentState = ${animationState.currentState}")
/*...*/
Button(
/*...*/
onClick = {
animationState.targetState = false
}
) {/*...*/}
}
My logs, quite unfortunately, are like this:
V/ANIMATION: View name is now Appearing
V/ANIMATION: View name has values: isIdle = false, currentState = false
V/ANIMATION: View name is now Appearing
V/ANIMATION: View name has values: isIdle = false, currentState = false
V/ANIMATION: View name is now Visible
V/ANIMATION: View name has values: isIdle = true, currentState = true
V/ANIMATION: View name is now Visible
V/ANIMATION: View name has values: isIdle = true, currentState = true
// After I click the button:
V/ANIMATION: View name is now Disappearing
V/ANIMATION: View name has values: isIdle = false, currentState = true
V/ANIMATION: View name is now Disappearing
V/ANIMATION: View name has values: isIdle = false, currentState = true
V/ANIMATION: View name is now Disappearing
V/ANIMATION: View name has values: isIdle = false, currentState = true
So where's the invisible state, i.e. false
currentState
? Have I done something wrong?
CodePudding user response:
Each time you change state, it triggers related views recomposition. And you set animationState
to true
on each recomposition with .apply { targetState = true }
.
Most likely you want to show it animated at the beginning, then you need to use LaunchedEffect
: it will be called only once when the view appears.
val animationState = remember {
MutableTransitionState(false)
}
LaunchedEffect(Unit) {
animationState.targetState = true
}
Read more about recompositions in Thinking in Compose and about state in Compose in state documentation.
CodePudding user response:
After some testing...
I tested this type of animation on a separate project and figured out that the AnimatedVisibility
content gets disposed before the animationState
emits its final value, so you can't possibly catch it from inside the view you are animating.
Following @Philip Dukhov advice, my LazyColumn
s item is now like this:
val animationState by remember {
mutableStateOf(MutableTransitionState(false))
}
LaunchedEffect(Unit) {
animationState.targetState = true
}
when(animationState.transitionState()) {
TransitionState.Invisible -> viewModel.deleteInvisibleItems()
else -> {}
}
Log.v("ANIMATION", "View ${item.name} is now ${animationState.transitionState().name}")
This catches emitted values perfectly.