diff --git a/documentation/ui/transitions.md b/documentation/ui/transitions.md index 9ca6a9507..05745bc3d 100644 --- a/documentation/ui/transitions.md +++ b/documentation/ui/transitions.md @@ -1,7 +1,7 @@ # Transitions - + You can have arbitrary visualisations and transitions for any [NavModel](../navmodel/index.md). For example, all of these are different representations of the same [Back stack](../navmodel/backstack.md): @@ -9,13 +9,185 @@ You can have arbitrary visualisations and transitions for any [NavModel](../navm -Below you can find the different options how to visualise `NavModel` state changes. +Below you can find the different options how to visualise `NavModel` state changes. ## No transitions -Using the provided [Child-related composables](children-view.md) you'll see no transitions as a default – UI changes resulting from the NavModel's state update will be rendered instantly. +Using the provided [Child-related composables](children-view.md) you'll see no transitions as a default – UI changes resulting from the NavModel's state update will be rendered instantly. + + +## Shared element transitions + +To support shared element transition between two Child Nodes you need: + +1. Use the `sharedElement` Modifier with the same key on the composable you want to connect. +2. On the `Children` composable, set `withSharedElementTransition` to true and use either fader or + no transition handler at all. Using a slider will make the shared element slide away with the + rest of of the content. +3. When operation is performed on the NavModel, the shared element will be animated between the two + Child Nodes. For instance, in the example below backStack currently has NavTarget.Child1 as the + active element. Performing a push operation with NavTarget.Child2 will animate the shared element + between NodeOne and NodeTwo. Popping back to NavTarget.Child1 will animate the shared element back. + +```kotlin +class NodeOne( + buildContext: BuildContext +) : Node( + buildContext = buildContext +) { + + @Composable + override fun View(modifier: Modifier) { + Box( + modifier = Modifier + // make sure you specify the size before using sharedElement modifier + .fillMaxSize() + .sharedElement(key = "sharedContainer") + ) { /** ... */ } + } +} + +class NodeTwo( + buildContext: BuildContext +) : Node( + buildContext = buildContext +) { + + @Composable + override fun View(modifier: Modifier) { + Box( + modifier = Modifier + // make sure you specify the size before using sharedElement modifier + .requiredSize(64.dp) + .sharedElement(key = "sharedContainer") + ) { /** ... */ } + } +} + +class ParentNode( + buildContext: BuildContext, + backStack: BackStack = BackStack( + initialElement = NavTarget.Child1, + savedStateMap = buildContext.savedStateMap + ) +) : ParentNode( + buildContext = buildContext, + navModel = backStack, +) { + + override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node = + when (navTarget) { + NavTarget.Child1 -> NodeOne(buildContext) + NavTarget.Child2 -> NodeTwo(buildContext) + } + + @Composable + override fun View(modifier: Modifier) { + Children( + // or any other NavModel + navModel = backStack, + // or no transitionHandler at all. Using a slider will make the shared element slide away + // with the rest of of the content. + transitionHandler = rememberBackStackFader(), + withSharedElementTransition = true + ) + } +} + +``` + +## Transitions with movable content + +You can move composable content between two Child Nodes without losing its state. You can only move +content from a Node that is currently visible and transitioning to invisible state to a Node that +is currently invisible and transitioning to visible state as movable content is intended to be +composed once design and is moved from one part of the composition to another. + +To move content between two Child Nodes you need to use `localMovableContentWithTargetVisibility` +composable function with the correct key to retrieve existing content if it exists or put content +for this key if it doesn't exist. In addition to that, on Parent's `Children` composable you need to +set `withMovableContent` to true. +In the example below when a NodeOne is being replaced with NodeTwo in a BackStack or Spotlight NavModel +`CustomMovableContent("movableContentKey")` will be moved from NodeOne to NodeTwo without losing its +state. + + +```kotlin +@Composable +fun CustomMovableContent(key: Any) { + localMovableContentWithTargetVisibility(key = key) { + // implement movable content here + var counter by remember(pageId) { mutableIntStateOf(0) } + LaunchedEffect(Unit) { + while (true) { + delay(1000) + counter++ + } + } + Text(text = "$counter") + }?.invoke() +} + + +class NodeOne( + buildContext: BuildContext +) : Node( + buildContext = buildContext +) { + + @Composable + override fun View(modifier: Modifier) { + CustomMovableContent("movableContentKey") + } + +} + +class NodeTwo( + buildContext: BuildContext +) : Node( + buildContext = buildContext +) { + + @Composable + override fun View(modifier: Modifier) { + CustomMovableContent("movableContentKey") + } +} + +class ParentNode( + buildContext: BuildContext, + backStack: BackStack = BackStack( + initialElement = NavTarget.Child1, + savedStateMap = buildContext.savedStateMap + ) +) : ParentNode( + buildContext = buildContext, + navModel = backStack, +) { + + override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node = + when (navTarget) { + NavTarget.Child1 -> NodeOne(buildContext) + NavTarget.Child2 -> NodeTwo(buildContext) + } + + @Composable + override fun View(modifier: Modifier) { + Children( + // or any other NavModel + navModel = backStack, + // or no transitionHandler at all. Using a slider will make the shared element slide away + // with the rest of of the content. + transitionHandler = rememberBackStackFader(), + withMovableContent = true, + withSharedElementTransition = true + ) + } +} + +``` ## Jetpack Compose default animations @@ -40,7 +212,7 @@ All the [child composables](children-view.md) provided by Appyx accept an option The benefit of using transition handlers is you can represent any custom state of elements defined by your NavModel with Compose `Modifiers`. -The example below is taken from [custom navigation models](../navmodel/custom.md). It matches custom transition states to different scaling values, and returns a `scale` `Modifier`. +The example below is taken from [custom navigation models](../navmodel/custom.md). It matches custom transition states to different scaling values, and returns a `scale` `Modifier`. ```kotlin class FooTransitionHandler( @@ -83,4 +255,4 @@ fun rememberFooTransitionHandler( 1. You can find more complex examples in the implementations of other NavModels, such as the [Promoter carousel](../navmodel/promoter.md) 2. You can find [Codelabs tutorials](../how-to-use-appyx/codelabs.md) that help you master custom transitions -3. You can find [Coding challenges](../how-to-use-appyx/coding-challenges.md) related to custom transitions +3. You can find [Coding challenges](../how-to-use-appyx/coding-challenges.md) related to custom transitions \ No newline at end of file