createChildTransition

Composable Function

Common
@ExperimentalTransitionApi
@Composable
public inline fun <S, T> Transition<S>.createChildTransition(
    label: String = "ChildTransition",
    transformToChildState: @Composable (parentState: S) -> T,
): Transition<T>

createChildTransition creates a child Transition based on the mapping between parent state to child state provided in transformToChildState. This serves the following purposes:

  1. Hoist the child transition state into parent transition. Therefore the parent Transition will be aware of whether there's any on-going animation due to the same target state change. This will further allow sequential animation to be set up when all animations have finished.
  2. Separation of concerns. The child transition can respresent a much more simplified state transition when, for example, mapping from an enum parent state to a Boolean visible state for passing further down the compose tree. The child composables hence can be designed around handling a more simple and a more relevant state change.

label is used to differentiate from other animations in the same transition in Android Studio.

Code Examples

CreateChildTransitionSample

@Composable
@Suppress("UNUSED_PARAMETER")
fun CreateChildTransitionSample() {
    // enum class DialerState { DialerMinimized, NumberPad }
    @OptIn(ExperimentalTransitionApi::class)
    @Composable
    fun DialerButton(visibilityTransition: Transition<Boolean>, modifier: Modifier) {
        val scale by visibilityTransition.animateFloat { visible -> if (visible) 1f else 2f }
        Box(modifier.scale(scale).background(Color.Black)) {
            // Content goes here
        }
    }
    @Composable
    fun NumberPad(visibilityTransition: Transition<Boolean>) {
        // Create animations using the provided Transition for visibility change here...
    }
    @OptIn(ExperimentalTransitionApi::class)
    @Composable
    fun childTransitionSample() {
        var dialerState by remember { mutableStateOf(DialerState.NumberPad) }
        Box(Modifier.fillMaxSize()) {
            val parentTransition = updateTransition(dialerState)
            // Animate to different corner radius based on target state
            val cornerRadius by
                parentTransition.animateDp { if (it == DialerState.NumberPad) 0.dp else 20.dp }
            Box(
                Modifier.align(Alignment.BottomCenter)
                    .widthIn(50.dp)
                    .heightIn(50.dp)
                    .clip(RoundedCornerShape(cornerRadius))
            ) {
                NumberPad(
                    // Creates a child transition that derives its target state from the parent
                    // transition, and the mapping from parent state to child state.
                    // This will allow:
                    // 1) Parent transition to account for additional animations in the child
                    // Transitions before it considers itself finished. This is useful when you
                    // have a subsequent action after all animations triggered by a state change
                    // have finished.
                    // 2) Separation of concerns. This allows the child composable (i.e.
                    // NumberPad) to only care about its own visibility, rather than knowing about
                    // DialerState.
                    visibilityTransition =
                        parentTransition.createChildTransition {
                            // This is the lambda that defines how the parent target state maps to
                            // child target state.
                            it == DialerState.NumberPad
                        }
                    // Note: If it's not important for the animations within the child composable to
                    // be observable, it's perfectly valid to not hoist the animations through
                    // a Transition object and instead use animate*AsState.
                )
                DialerButton(
                    visibilityTransition =
                        parentTransition.createChildTransition {
                            it == DialerState.DialerMinimized
                        },
                    modifier = Modifier.matchParentSize(),
                )
            }
        }
    }
}