1 /*
2 * Copyright 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.compose.animation.scene
18
19 import androidx.compose.runtime.Composable
20 import androidx.compose.runtime.State
21 import androidx.compose.runtime.remember
22 import androidx.compose.ui.Modifier
23 import androidx.compose.ui.platform.LocalDensity
24
25 /**
26 * [SceneTransitionLayout] is a container that automatically animates its content whenever
27 * [currentScene] changes, using the transitions defined in [transitions].
28 *
29 * Note: You should use [androidx.compose.animation.AnimatedContent] instead of
30 * [SceneTransitionLayout] if it fits your need. Use [SceneTransitionLayout] over AnimatedContent if
31 * you need support for swipe gestures, shared elements or transitions defined declaratively outside
32 * UI code.
33 *
34 * @param currentScene the current scene
35 * @param onChangeScene a mutator that should set [currentScene] to the given scene when called.
36 * This is called when the user commits a transition to a new scene because of a [UserAction], for
37 * instance by triggering back navigation or by swiping to a new scene.
38 * @param transitions the definition of the transitions used to animate a change of scene.
39 * @param state the observable state of this layout.
40 * @param scenes the configuration of the different scenes of this layout.
41 */
42 @Composable
SceneTransitionLayoutnull43 fun SceneTransitionLayout(
44 currentScene: SceneKey,
45 onChangeScene: (SceneKey) -> Unit,
46 transitions: SceneTransitions,
47 modifier: Modifier = Modifier,
48 state: SceneTransitionLayoutState = remember { SceneTransitionLayoutState(currentScene) },
49 scenes: SceneTransitionLayoutScope.() -> Unit,
50 ) {
51 val density = LocalDensity.current
<lambda>null52 val layoutImpl = remember {
53 SceneTransitionLayoutImpl(
54 onChangeScene,
55 scenes,
56 transitions,
57 state,
58 density,
59 )
60 }
61
62 layoutImpl.onChangeScene = onChangeScene
63 layoutImpl.transitions = transitions
64 layoutImpl.density = density
65 layoutImpl.setScenes(scenes)
66 layoutImpl.setCurrentScene(currentScene)
67
68 layoutImpl.Content(modifier)
69 }
70
71 interface SceneTransitionLayoutScope {
72 /**
73 * Add a scene to this layout, identified by [key].
74 *
75 * You can configure [userActions] so that swiping on this layout or navigating back will
76 * transition to a different scene.
77 *
78 * Important: scene order along the z-axis follows call order. Calling scene(A) followed by
79 * scene(B) will mean that scene B renders after/above scene A.
80 */
scenenull81 fun scene(
82 key: SceneKey,
83 userActions: Map<UserAction, SceneKey> = emptyMap(),
84 content: @Composable SceneScope.() -> Unit,
85 )
86 }
87
88 interface SceneScope {
89 /**
90 * Tag an element identified by [key].
91 *
92 * Tagging an element will allow you to reference that element when defining transitions, so
93 * that the element can be transformed and animated when the scene transitions in or out.
94 *
95 * Additionally, this [key] will be used to detect elements that are shared between scenes to
96 * automatically interpolate their size, offset and [shared values][animateSharedValueAsState].
97 *
98 * TODO(b/291566282): Migrate this to the new Modifier Node API and remove the @Composable
99 * constraint.
100 */
101 @Composable fun Modifier.element(key: ElementKey): Modifier
102
103 /**
104 * Animate some value of a shared element.
105 *
106 * @param value the value of this shared value in the current scene.
107 * @param key the key of this shared value.
108 * @param element the element associated with this value.
109 * @param lerp the *linear* interpolation function that should be used to interpolate between
110 * two different values. Note that it has to be linear because the [fraction] passed to this
111 * interpolator is already interpolated.
112 * @param canOverflow whether this value can overflow past the values it is interpolated
113 * between, for instance because the transition is animated using a bouncy spring.
114 * @see animateSharedIntAsState
115 * @see animateSharedFloatAsState
116 * @see animateSharedDpAsState
117 * @see animateSharedColorAsState
118 */
119 @Composable
120 fun <T> animateSharedValueAsState(
121 value: T,
122 key: ValueKey,
123 element: ElementKey,
124 lerp: (start: T, stop: T, fraction: Float) -> T,
125 canOverflow: Boolean,
126 ): State<T>
127 }
128
129 /** An action performed by the user. */
130 sealed interface UserAction
131
132 /** The user navigated back, either using a gesture or by triggering a KEYCODE_BACK event. */
133 object Back : UserAction
134
135 /** The user swiped on the container. */
136 enum class Swipe : UserAction {
137 Up,
138 Down,
139 Left,
140 Right,
141 }
142