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 43 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 52 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 */ 81 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