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