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:
- 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.
- 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(),
)
}
}
}
}