SeekableTransitionState


A TransitionState that can manipulate the progress of the Transition by seeking with seekTo or animating with animateTo.

A SeekableTransitionState can only be used with one Transition instance. Once assigned, it cannot be reassigned to a different Transition instance.

import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.SeekableTransitionState
import androidx.compose.animation.core.Transition
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.rememberTransition
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.systemGestureExclusion
import androidx.compose.material.Button
import androidx.compose.material.Slider
import androidx.compose.material.Text
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.unit.dp

Column {
    val seekingState = remember { SeekableTransitionState(BoxSize.Small) }
    val scope = rememberCoroutineScope()
    Column {
        Row {
            Button(
                onClick = { scope.launch { seekingState.animateTo(BoxSize.Small) } },
                Modifier.wrapContentWidth().weight(1f)
            ) {
                Text("Animate Small")
            }
            Button(
                onClick = { scope.launch { seekingState.seekTo(0f, BoxSize.Small) } },
                Modifier.wrapContentWidth().weight(1f)
            ) {
                Text("Seek Small")
            }
            Button(
                onClick = { scope.launch { seekingState.seekTo(0f, BoxSize.Medium) } },
                Modifier.wrapContentWidth().weight(1f)
            ) {
                Text("Seek Medium")
            }
            Button(
                onClick = { scope.launch { seekingState.seekTo(0f, BoxSize.Large) } },
                Modifier.wrapContentWidth().weight(1f)
            ) {
                Text("Seek Large")
            }
            Button(
                onClick = { scope.launch { seekingState.animateTo(BoxSize.Large) } },
                Modifier.wrapContentWidth().weight(1f)
            ) {
                Text("Animate Large")
            }
        }
    }
    Slider(
        value = seekingState.fraction,
        modifier = Modifier.systemGestureExclusion().padding(10.dp),
        onValueChange = { value -> scope.launch { seekingState.seekTo(fraction = value) } }
    )
    val transition = rememberTransition(seekingState)

    val scale: Float by
        transition.animateFloat(
            transitionSpec = { tween(easing = LinearEasing) },
            label = "Scale"
        ) { state ->
            when (state) {
                BoxSize.Small -> 1f
                BoxSize.Medium -> 2f
                BoxSize.Large -> 3f
            }
        }

    transition.AnimatedContent(
        transitionSpec = {
            fadeIn(tween(easing = LinearEasing)) togetherWith
                fadeOut(tween(easing = LinearEasing))
        }
    ) { state ->
        if (state == BoxSize.Large) {
            Box(Modifier.size(50.dp).background(Color.Magenta))
        } else {
            Box(Modifier.size(50.dp))
        }
    }
    Box(
        Modifier.fillMaxSize()
            .wrapContentSize(Alignment.Center)
            .size(100.dp)
            .graphicsLayer {
                scaleX = scale
                scaleY = scale
            }
            .background(Color.Blue)
    )
}

Summary

Public constructors

<S : Any?> SeekableTransitionState(initialState: S)
Cmn

Public functions

suspend Unit
animateTo(targetState: S, animationSpec: FiniteAnimationSpec<Float>?)

Updates the current targetState to targetState and begins an animation to the new state.

Cmn
suspend Unit
seekTo(fraction: @FloatRange(from = 0.0, to = 1.0) Float, targetState: S)

Starts seeking the transition to targetState with fraction used to indicate the progress towards targetState.

Cmn
suspend Unit
snapTo(targetState: S)

Sets currentState and targetState to targetState and snaps all values to those at that state.

Cmn

Public properties

open S

Current state of the transition.

Cmn
Float

The progress of the transition from currentState to targetState as a fraction of the entire duration.

Cmn
open S

Target state of the transition.

Cmn

Public constructors

SeekableTransitionState

<S : Any?> SeekableTransitionState(initialState: S)

Public functions

animateTo

suspend fun animateTo(
    targetState: S = this.targetState,
    animationSpec: FiniteAnimationSpec<Float>? = null
): Unit

Updates the current targetState to targetState and begins an animation to the new state. If the current targetState is the same as targetState then the current transition animation is continued. If a previous transition was interrupted, currentState is changed to the former targetState and the start values are animated toward the former targetState.

Upon completion of the animation, currentState will be changed to targetState.

Parameters
targetState: S = this.targetState

The state to animate towards.

animationSpec: FiniteAnimationSpec<Float>? = null

If provided, is used to animate the animation fraction. If null, the transition is linearly traversed based on the duration of the transition.

seekTo

suspend fun seekTo(
    fraction: @FloatRange(from = 0.0, to = 1.0) Float,
    targetState: S = this.targetState
): Unit

Starts seeking the transition to targetState with fraction used to indicate the progress towards targetState. If the previous targetState was already targetState then seekTo only stops any current animation towards that state and snaps the fraction to the new value. Otherwise, the currentState is changed to the former targetState and targetState is changed to targetState and an animation is started, moving the start values towards the former targetState. This will return when the initial values have reached currentState and the fraction has been reached.

snapTo also allows the developer to change the state, but does not animate any values. Instead, it instantly moves all values to those at the new targetState.

import androidx.compose.animation.core.SeekableTransitionState
import androidx.compose.animation.core.Transition
import androidx.compose.animation.core.rememberTransition
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.systemGestureExclusion
import androidx.compose.material.Slider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

val seekingState = remember { SeekableTransitionState(BoxSize.Small) }
LaunchedEffect(seekingState.targetState) { seekingState.seekTo(0f, BoxSize.Large) }
val scope = rememberCoroutineScope()
Slider(
    value = seekingState.fraction,
    modifier = Modifier.systemGestureExclusion().padding(10.dp),
    onValueChange = { value -> scope.launch { seekingState.seekTo(fraction = value) } }
)
val transition = rememberTransition(seekingState)
// use the transition
See also
animateTo

snapTo

suspend fun snapTo(targetState: S): Unit

Sets currentState and targetState to targetState and snaps all values to those at that state. The transition will not have any animations running after running snapTo.

This can have a similar effect as seekTo. However, seekTo moves the currentState to the former targetState and animates the initial values of the animations from the current values to those at currentState. seekTo also allows the developer to move the state between any fraction between currentState and targetState, while snapTo moves all state to targetState without any further seeking allowed.

import androidx.compose.animation.core.SeekableTransitionState
import androidx.compose.animation.core.Transition
import androidx.compose.animation.core.rememberTransition
import androidx.compose.animation.core.snap
import androidx.compose.foundation.layout.Box
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope

val seekingState = remember { SeekableTransitionState(BoxSize.Small) }
val scope = rememberCoroutineScope()
Button(onClick = { scope.launch { seekingState.snapTo(BoxSize.Large) } }) {
    Text("Snap to the Small state")
}
val transition = rememberTransition(seekingState)
// use the transition
See also
animateTo

Public properties

currentState

open val currentState: S

Current state of the transition. If there is an active transition, currentState and targetState are different.

fraction

val fractionFloat

The progress of the transition from currentState to targetState as a fraction of the entire duration.

If targetState and currentState are the same, fraction will be 0.

targetState

open val targetState: S

Target state of the transition. If this is the same as currentState, no transition is active.