1 /*
<lambda>null2  * Copyright 2021 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  *      https://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 @file:Suppress("DEPRECATION")
18 
19 package com.google.accompanist.sample.navigation.animation
20 
21 import android.os.Bundle
22 import androidx.activity.ComponentActivity
23 import androidx.activity.compose.setContent
24 import androidx.compose.animation.AnimatedContentTransitionScope
25 import androidx.compose.animation.AnimatedVisibilityScope
26 import androidx.compose.animation.ExitTransition
27 import androidx.compose.animation.ExperimentalAnimationApi
28 import androidx.compose.animation.core.FiniteAnimationSpec
29 import androidx.compose.animation.core.LinearOutSlowInEasing
30 import androidx.compose.animation.core.tween
31 import androidx.compose.animation.expandIn
32 import androidx.compose.animation.fadeIn
33 import androidx.compose.animation.shrinkOut
34 import androidx.compose.animation.slideInVertically
35 import androidx.compose.animation.slideOutVertically
36 import androidx.compose.foundation.background
37 import androidx.compose.foundation.layout.Column
38 import androidx.compose.foundation.layout.Spacer
39 import androidx.compose.foundation.layout.fillMaxSize
40 import androidx.compose.foundation.layout.fillMaxWidth
41 import androidx.compose.foundation.layout.height
42 import androidx.compose.foundation.layout.wrapContentWidth
43 import androidx.compose.material.Button
44 import androidx.compose.material.ButtonDefaults
45 import androidx.compose.material.Text
46 import androidx.compose.runtime.Composable
47 import androidx.compose.ui.Alignment
48 import androidx.compose.ui.Modifier
49 import androidx.compose.ui.graphics.Color
50 import androidx.compose.ui.platform.LocalLifecycleOwner
51 import androidx.compose.ui.text.style.TextAlign
52 import androidx.compose.ui.unit.Dp
53 import androidx.compose.ui.unit.IntOffset
54 import androidx.compose.ui.unit.sp
55 import androidx.compose.ui.zIndex
56 import androidx.navigation.NavController
57 import androidx.navigation.NavHostController
58 import com.google.accompanist.navigation.animation.AnimatedNavHost
59 import com.google.accompanist.navigation.animation.composable
60 import com.google.accompanist.navigation.animation.navigation
61 import com.google.accompanist.navigation.animation.rememberAnimatedNavController
62 import com.google.accompanist.sample.AccompanistSampleTheme
63 
64 @ExperimentalAnimationApi
65 class AnimatedNavHostSample : ComponentActivity() {
66     override fun onCreate(savedInstanceState: Bundle?) {
67         super.onCreate(savedInstanceState)
68 
69         setContent {
70             AccompanistSampleTheme {
71                 val navController = rememberAnimatedNavController()
72                 AnimatedNavHost(navController, "select") {
73                     composable("select") {
74                         Column {
75                             Button(onClick = { navController.navigate("sample") }) {
76                                 Text("AnimationNav")
77                             }
78                             Button(onClick = { navController.navigate("zOrder") }) {
79                                 Text("Z-ordered Animations")
80                             }
81                         }
82                     }
83                     composable("sample") {
84                         ExperimentalAnimationNav()
85                     }
86                     composable("zOrder") {
87                         NavTestScreen()
88                     }
89                 }
90             }
91         }
92     }
93 }
94 
95 @ExperimentalAnimationApi
96 @Composable
ExperimentalAnimationNavnull97 fun ExperimentalAnimationNav() {
98     val navController = rememberAnimatedNavController()
99     AnimatedNavHost(navController, startDestination = "Blue") {
100         composable(
101             "Blue",
102             enterTransition = {
103                 when (initialState.destination.route) {
104                     "Red" ->
105                         slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Left, animationSpec = tween(700))
106                     else -> null
107                 }
108             },
109             exitTransition = {
110                 when (targetState.destination.route) {
111                     "Red" ->
112                         slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Left, animationSpec = tween(700))
113                     else -> null
114                 }
115             },
116             popEnterTransition = {
117                 when (initialState.destination.route) {
118                     "Red" ->
119                         slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Right, animationSpec = tween(700))
120                     else -> null
121                 }
122             },
123             popExitTransition = {
124                 when (targetState.destination.route) {
125                     "Red" ->
126                         slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Right, animationSpec = tween(700))
127                     else -> null
128                 }
129             }
130         ) { BlueScreen(navController) }
131         composable(
132             "Red",
133             enterTransition = {
134                 when (initialState.destination.route) {
135                     "Blue" ->
136                         slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Left, animationSpec = tween(700))
137                     "Green" ->
138                         slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Up, animationSpec = tween(700))
139                     else -> null
140                 }
141             },
142             exitTransition = {
143                 when (targetState.destination.route) {
144                     "Blue" ->
145                         slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Left, animationSpec = tween(700))
146                     "Green" ->
147                         slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Up, animationSpec = tween(700))
148                     else -> null
149                 }
150             },
151             popEnterTransition = {
152                 when (initialState.destination.route) {
153                     "Blue" ->
154                         slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Right, animationSpec = tween(700))
155                     "Green" ->
156                         slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Down, animationSpec = tween(700))
157                     else -> null
158                 }
159             },
160             popExitTransition = {
161                 when (targetState.destination.route) {
162                     "Blue" ->
163                         slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Right, animationSpec = tween(700))
164                     "Green" ->
165                         slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Down, animationSpec = tween(700))
166                     else -> null
167                 }
168             }
169         ) { RedScreen(navController) }
170         navigation(
171             startDestination = "Green",
172             route = "Inner",
173             enterTransition = { expandIn(animationSpec = tween(700)) },
174             exitTransition = { shrinkOut(animationSpec = tween(700)) }
175         ) {
176             composable(
177                 "Green",
178                 enterTransition = {
179                     when (initialState.destination.route) {
180                         "Red" ->
181                             slideIntoContainer(
182                                 AnimatedContentTransitionScope.SlideDirection.Up, animationSpec = tween(700)
183                             )
184                         else -> null
185                     }
186                 },
187                 exitTransition = {
188                     when (targetState.destination.route) {
189                         "Red" ->
190                             slideOutOfContainer(
191                                 AnimatedContentTransitionScope.SlideDirection.Up, animationSpec = tween(700)
192                             )
193                         else -> null
194                     }
195                 },
196                 popEnterTransition = {
197                     when (initialState.destination.route) {
198                         "Red" ->
199                             slideIntoContainer(
200                                 AnimatedContentTransitionScope.SlideDirection.Down, animationSpec = tween(700)
201                             )
202                         else -> null
203                     }
204                 },
205                 popExitTransition = {
206                     when (targetState.destination.route) {
207                         "Red" ->
208                             slideOutOfContainer(
209                                 AnimatedContentTransitionScope.SlideDirection.Down, animationSpec = tween(700)
210                             )
211                         else -> null
212                     }
213                 }
214             ) { GreenScreen(navController) }
215         }
216     }
217 }
218 
219 @ExperimentalAnimationApi
220 @Composable
BlueScreennull221 fun AnimatedVisibilityScope.BlueScreen(navController: NavHostController) {
222     Column(
223         Modifier
224             .fillMaxSize()
225             .background(Color.Blue)
226     ) {
227         Spacer(Modifier.height(Dp(25f)))
228         NavigateButton(
229             "Navigate Horizontal",
230             Modifier
231                 .wrapContentWidth()
232                 .then(Modifier.align(Alignment.CenterHorizontally))
233         ) { navController.navigate("Red") }
234         Spacer(Modifier.height(Dp(25f)))
235         NavigateButton(
236             "Navigate Expand",
237             Modifier
238                 .wrapContentWidth()
239                 .then(Modifier.align(Alignment.CenterHorizontally))
240         ) { navController.navigate("Inner") }
241         Text(
242             "Blue",
243             modifier = Modifier.fillMaxWidth().weight(1f).animateEnterExit(
244                 enter = fadeIn(animationSpec = tween(250, delayMillis = 450)),
245                 exit = ExitTransition.None
246             ),
247             color = Color.White, fontSize = 80.sp, textAlign = TextAlign.Center
248         )
249         NavigateBackButton(navController)
250     }
251 }
252 
253 @ExperimentalAnimationApi
254 @Composable
RedScreennull255 fun AnimatedVisibilityScope.RedScreen(navController: NavHostController) {
256     Column(
257         Modifier
258             .fillMaxSize()
259             .background(Color.Red)
260     ) {
261         Spacer(Modifier.height(Dp(25f)))
262         NavigateButton(
263             "Navigate Horizontal",
264             Modifier
265                 .wrapContentWidth()
266                 .then(Modifier.align(Alignment.CenterHorizontally))
267         ) { navController.navigate("Blue") }
268         Spacer(Modifier.height(Dp(25f)))
269         NavigateButton(
270             "Navigate Vertical",
271             Modifier
272                 .wrapContentWidth()
273                 .then(Modifier.align(Alignment.CenterHorizontally))
274         ) { navController.navigate("Green") }
275         Text(
276             "Red",
277             modifier = Modifier.fillMaxWidth().weight(1f).animateEnterExit(
278                 enter = fadeIn(animationSpec = tween(250, delayMillis = 450)),
279                 exit = ExitTransition.None
280             ),
281             color = Color.White, fontSize = 80.sp, textAlign = TextAlign.Center
282         )
283         NavigateBackButton(navController)
284     }
285 }
286 
287 @ExperimentalAnimationApi
288 @Composable
AnimatedVisibilityScopenull289 fun AnimatedVisibilityScope.GreenScreen(navController: NavHostController) {
290     Column(
291         Modifier
292             .fillMaxSize()
293             .background(Color.Green)
294     ) {
295         Spacer(Modifier.height(Dp(25f)))
296         NavigateButton(
297             "Navigate to Red",
298             Modifier
299                 .wrapContentWidth()
300                 .then(Modifier.align(Alignment.CenterHorizontally))
301         ) { navController.navigate("Red") }
302         Text(
303             "Green",
304             modifier = Modifier.fillMaxWidth().weight(1f).animateEnterExit(
305                 enter = fadeIn(animationSpec = tween(250, delayMillis = 450)),
306                 exit = ExitTransition.None
307             ),
308             color = Color.White, fontSize = 80.sp, textAlign = TextAlign.Center
309         )
310         NavigateBackButton(navController)
311     }
312 }
313 
314 @Composable
NavigateButtonnull315 fun NavigateButton(
316     text: String,
317     modifier: Modifier = Modifier,
318     listener: () -> Unit = { }
319 ) {
320     Button(
321         onClick = listener,
322         colors = ButtonDefaults.buttonColors(backgroundColor = Color.LightGray),
323         modifier = modifier
<lambda>null324     ) {
325         Text(text = text)
326     }
327 }
328 
329 @Composable
NavigateBackButtonnull330 fun NavigateBackButton(navController: NavController) {
331     // Use LocalLifecycleOwner.current as a proxy for the NavBackStackEntry
332     // associated with this Composable
333     if (navController.currentBackStackEntry == LocalLifecycleOwner.current &&
334         navController.previousBackStackEntry != null
335     ) {
336         Button(
337             onClick = { navController.popBackStack() },
338             colors = ButtonDefaults.buttonColors(backgroundColor = Color.LightGray),
339             modifier = Modifier.fillMaxWidth()
340         ) {
341             Text(text = "Go to Previous screen")
342         }
343     }
344 }
345 
346 object Destinations {
347     const val First = "first"
348     const val Second = "second"
349     const val Third = "third"
350 }
351 @ExperimentalAnimationApi
352 @Composable
NavTestScreennull353 fun NavTestScreen() {
354     val navController = rememberAnimatedNavController()
355     AnimatedNavHost(
356         navController = navController,
357         startDestination = Destinations.First,
358         modifier = Modifier.fillMaxSize()
359     ) {
360         composable(
361             Destinations.First,
362             enterTransition = { NavigationTransition.slideInBottomAnimation },
363             popEnterTransition = { NavigationTransition.IdentityEnter },
364             exitTransition = { NavigationTransition.IdentityExit },
365             popExitTransition = { NavigationTransition.slideOutBottomAnimation },
366         ) {
367             Button(onClick = {
368                 navController.navigate(Destinations.Second)
369             }) {
370                 Text(text = "First")
371             }
372         }
373         composable(
374             route = Destinations.Second,
375             enterTransition = { NavigationTransition.slideInBottomAnimation },
376             popEnterTransition = { NavigationTransition.IdentityEnter },
377             exitTransition = { NavigationTransition.IdentityExit },
378             popExitTransition = { NavigationTransition.slideOutBottomAnimation },
379         ) {
380             Button(
381                 onClick = {
382                     navController.navigate(Destinations.Third)
383                 },
384                 colors = ButtonDefaults.buttonColors(backgroundColor = Color.Yellow),
385                 modifier = Modifier.zIndex(100f)
386             ) {
387                 Text(text = "Second")
388             }
389         }
390         composable(
391             route = Destinations.Third,
392             enterTransition = { NavigationTransition.slideInBottomAnimation },
393             popEnterTransition = { NavigationTransition.IdentityEnter },
394             exitTransition = { NavigationTransition.IdentityExit },
395             popExitTransition = { NavigationTransition.slideOutBottomAnimation },
396         ) {
397             Button(
398                 onClick = {
399                     navController.popBackStack()
400                 },
401                 colors = ButtonDefaults.buttonColors(backgroundColor = Color.Blue),
402                 modifier = Modifier.zIndex(100f)
403             ) {
404                 Text(text = "Third")
405             }
406         }
407     }
408 }
409 
410 object NavigationTransition {
411     private val animation: FiniteAnimationSpec<IntOffset> = tween(
412         easing = LinearOutSlowInEasing,
413         durationMillis = 2000,
414     )
415 
416     val IdentityEnter = slideInVertically(
<lambda>null417         initialOffsetY = {
418             -1 // fix for https://github.com/google/accompanist/issues/1159
419         },
420         animationSpec = animation
421     )
422 
423     val IdentityExit = slideOutVertically(
<lambda>null424         targetOffsetY = {
425             -1 // fix for https://github.com/google/accompanist/issues/1159
426         },
427         animationSpec = animation
428     )
429 
430     var slideInBottomAnimation =
fullHeightnull431         slideInVertically(initialOffsetY = { fullHeight -> fullHeight }, animationSpec = animation)
432 
433     var slideOutBottomAnimation =
fullHeightnull434         slideOutVertically(targetOffsetY = { fullHeight -> fullHeight }, animationSpec = animation)
435 }
436