blob: fc1b3c1c3ff990dda392dcb0124f8e124a07c56c [file] [log] [blame]
package com.airbnb.lottie.compose
import androidx.compose.runtime.*
import com.airbnb.lottie.LottieComposition
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collectLatest
/**
* [lottieTransition] allows you to define individual animations mapped to a set of states.
*
* Use this version if your transition is defined by separate segments from within a single composition
*
* Inside of [animate], you will probably want to use the suspending version of [animateLottieComposition].
*
* To loop an animation within a transition, just wrap [animateLottieComposition] in a loop.
*
* Example usage:
* ```
* var state by remember { mutableStateOf(0) }
* val compositionResult = lottieComposition(LottieCompositionSpec.RawRes(R.raw.your_animation))
* val progress = lottieTransition(state) { progress ->
* val composition = compositionResult.await()
* when (state) {
* 0 -> animateLottieComposition(
* composition,
* progress,
* clipSpec = LottieAnimationClipSpec.MinAndMaxProgress(0f, 0.5f),
* cancellationBehavior = LottieCancellationBehavior.AtEnd,
* )
* 1 -> animateLottieComposition(
* composition,
* progress,
* clipSpec = LottieAnimationClipSpec.MinAndMaxProgress(0.5f, 1f),
* cancellationBehavior = LottieCancellationBehavior.AtEnd,
* )
* }
* }
* LottieAnimation(
* compositionResult(),
* progress,
* )
* ```
*
* @see animateLottieComposition
*/
@Composable
fun <T> lottieTransition(
state: T,
animate: suspend CoroutineScope.(progress: MutableState<Float>) -> Unit,
): Float {
val progress = remember { mutableStateOf(0f) }
val states = remember { MutableStateFlow(state) }
states.value = state
val currentAnimate by rememberUpdatedState(animate)
LaunchedEffect(Unit) {
// We use collectLatest instead of LaunchedEffect with key = state because
// we want to allow `animate` to use a NonCancellable CoroutineContext
// in case it wants to finish its animation before the next state's animation
states.collectLatest {
coroutineScope {
currentAnimate(progress)
}
}
}
return progress.value
}
/**
* State returned from [lottieTransition] when your transition consists of multiple compositions.
*/
class LottieTransitionState {
@Suppress("PropertyName")
internal val _progress = mutableStateOf(0f)
@Suppress("PropertyName")
internal val _composition = mutableStateOf(LottieCompositionResult())
val composition: LottieCompositionResult get() = _composition.value
val progress: Float get() = _progress.value
operator fun component1() = composition
operator fun component2() = progress
}
/**
* [lottieTransition] allows you to define individual animations mapped to a set of states.
*
* Use this version if your transition is split up into several compositions.
*
* Inside of [animate], you will probably want to use the suspending version of [animateLottieComposition].
*
* To loop an animation within a transition, just wrap [animateLottieComposition] in a loop.
*
* Example usage:
* ```
* var state by remember { mutableStateOf(0) }
* val compositionResult1 = lottieComposition(LottieCompositionSpec.RawRes(R.raw.your_animation_1))
* val compositionResult2 = lottieComposition(LottieCompositionSpec.RawRes(R.raw.your_animation_2))
*
* val (compositionResult, progress) = lottieTransition(
* state,
* compositionForState = {
* when (state) {
* 0 -> compositionResult1
* else -> compositionResult2
* }
* },
* ) { compositionResult, progress ->
* val composition = compositionResult.await()
* when (state) {
* 0 -> animateLottieComposition(
* composition,
* progress,
* clipSpec = LottieAnimationClipSpec.MinAndMaxProgress(0f, 0.5f),
* cancellationBehavior = LottieCancellationBehavior.AtEnd,
* )
* 1 -> animateLottieComposition(
* composition,
* progress,
* clipSpec = LottieAnimationClipSpec.MinAndMaxProgress(0.5f, 1f),
* cancellationBehavior = LottieCancellationBehavior.AtEnd,
* )
* }
* }
* LottieAnimation(
* compositionResult(),
* progress,
* )
* ```
*
* @see animateLottieComposition
*/
@Composable
fun <T> lottieTransition(
state: T,
compositionForState: suspend () -> LottieCompositionResult,
animate: suspend CoroutineScope.(composition: LottieCompositionResult, progress: MutableState<Float>) -> Unit,
): LottieTransitionState {
val transitionState = remember { LottieTransitionState() }
val states = remember { MutableStateFlow(state) }
states.value = state
val currentCompositionForState by rememberUpdatedState(compositionForState)
val currentAnimate by rememberUpdatedState(animate)
LaunchedEffect(Unit) {
// We use collectLatest instead of LaunchedEffect with key = state because
// we want to allow `animate` to use a NonCancellable CoroutineContext
// in case it wants to finish its animation before the next state's animation
// starts.
// LaunchedEffect won't wait for the NonCancellable job to finish when the
// new state starts.
states.collectLatest {
val compositionResult = currentCompositionForState()
transitionState._composition.value = compositionResult
coroutineScope {
currentAnimate(compositionResult, transitionState._progress)
}
}
}
return transitionState
}