Skip to content

Commit 7cdae30

Browse files
add navigate back with drag sample
1 parent 3f1cf64 commit 7cdae30

File tree

1 file changed

+308
-0
lines changed

1 file changed

+308
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
@file:OptIn(ExperimentalMaterial3Api::class)
2+
3+
package com.smarttoolfactory.tutorial3_1navigation
4+
5+
import androidx.activity.compose.BackHandler
6+
import androidx.compose.animation.AnimatedContentTransitionScope
7+
import androidx.compose.animation.EnterTransition
8+
import androidx.compose.animation.ExitTransition
9+
import androidx.compose.animation.core.Animatable
10+
import androidx.compose.animation.core.tween
11+
import androidx.compose.animation.fadeIn
12+
import androidx.compose.foundation.background
13+
import androidx.compose.foundation.clickable
14+
import androidx.compose.foundation.gestures.detectHorizontalDragGestures
15+
import androidx.compose.foundation.layout.Arrangement
16+
import androidx.compose.foundation.layout.Box
17+
import androidx.compose.foundation.layout.BoxWithConstraints
18+
import androidx.compose.foundation.layout.Column
19+
import androidx.compose.foundation.layout.PaddingValues
20+
import androidx.compose.foundation.layout.Row
21+
import androidx.compose.foundation.layout.Spacer
22+
import androidx.compose.foundation.layout.fillMaxSize
23+
import androidx.compose.foundation.layout.fillMaxWidth
24+
import androidx.compose.foundation.layout.padding
25+
import androidx.compose.foundation.layout.systemBarsPadding
26+
import androidx.compose.foundation.lazy.LazyColumn
27+
import androidx.compose.foundation.lazy.rememberLazyListState
28+
import androidx.compose.material.icons.Icons
29+
import androidx.compose.material.icons.automirrored.filled.ArrowBack
30+
import androidx.compose.material3.Card
31+
import androidx.compose.material3.CardDefaults
32+
import androidx.compose.material3.ExperimentalMaterial3Api
33+
import androidx.compose.material3.Icon
34+
import androidx.compose.material3.IconButton
35+
import androidx.compose.material3.Text
36+
import androidx.compose.material3.TopAppBar
37+
import androidx.compose.runtime.Composable
38+
import androidx.compose.runtime.LaunchedEffect
39+
import androidx.compose.runtime.getValue
40+
import androidx.compose.runtime.mutableStateOf
41+
import androidx.compose.runtime.remember
42+
import androidx.compose.runtime.rememberCoroutineScope
43+
import androidx.compose.runtime.setValue
44+
import androidx.compose.runtime.snapshotFlow
45+
import androidx.compose.ui.Alignment
46+
import androidx.compose.ui.Modifier
47+
import androidx.compose.ui.graphics.Color
48+
import androidx.compose.ui.graphics.graphicsLayer
49+
import androidx.compose.ui.input.pointer.pointerInput
50+
import androidx.compose.ui.tooling.preview.Preview
51+
import androidx.compose.ui.unit.dp
52+
import androidx.compose.ui.unit.sp
53+
import androidx.compose.ui.zIndex
54+
import androidx.navigation.compose.NavHost
55+
import androidx.navigation.compose.composable
56+
import androidx.navigation.compose.rememberNavController
57+
import kotlinx.coroutines.flow.collectLatest
58+
import kotlinx.coroutines.flow.map
59+
import kotlinx.coroutines.launch
60+
61+
@Preview
62+
@Composable
63+
private fun App() {
64+
65+
val navController = rememberNavController()
66+
67+
NavHost(
68+
modifier = Modifier.systemBarsPadding().fillMaxSize(),
69+
navController = navController,
70+
enterTransition = { EnterTransition.None },
71+
exitTransition = { ExitTransition.None },
72+
startDestination = Routes.SETTINGS_ROUTE
73+
) {
74+
75+
composable(Routes.HOME_ROUTE) {
76+
MainScreen {
77+
navController.navigate(Routes.SETTINGS_ROUTE)
78+
}
79+
}
80+
composable(
81+
route = Routes.SETTINGS_ROUTE,
82+
enterTransition = {
83+
slideIntoContainer(
84+
AnimatedContentTransitionScope.SlideDirection.Right,
85+
animationSpec = tween(300)
86+
)
87+
},
88+
exitTransition = {
89+
slideOutOfContainer(
90+
AnimatedContentTransitionScope.SlideDirection.Left,
91+
animationSpec = tween(50)
92+
)
93+
},
94+
popEnterTransition = {
95+
fadeIn(animationSpec = tween(50))
96+
},
97+
popExitTransition = {
98+
slideOutOfContainer(
99+
AnimatedContentTransitionScope.SlideDirection.Right,
100+
animationSpec = tween(300)
101+
)
102+
}
103+
) {
104+
105+
val coroutineScope = rememberCoroutineScope()
106+
val offsetX = remember {
107+
Animatable(0f)
108+
}
109+
110+
var isDetailScreen by remember {
111+
mutableStateOf(false)
112+
}
113+
114+
var data by remember {
115+
mutableStateOf<Holder?>(null)
116+
}
117+
118+
LaunchedEffect(Unit) {
119+
snapshotFlow {
120+
offsetX.value
121+
}
122+
.map { it != 0f }
123+
.collectLatest {
124+
isDetailScreen = it
125+
}
126+
}
127+
128+
BackHandler(isDetailScreen) {
129+
coroutineScope.launch {
130+
offsetX.animateTo(
131+
0f,
132+
animationSpec = tween(durationMillis = 300)
133+
)
134+
}
135+
}
136+
137+
Column {
138+
139+
TopAppBar(
140+
navigationIcon = {
141+
IconButton(
142+
onClick = {
143+
if (isDetailScreen) {
144+
coroutineScope.launch {
145+
offsetX.animateTo(
146+
0f,
147+
animationSpec = tween(durationMillis = 300)
148+
)
149+
}
150+
}
151+
}
152+
) {
153+
Icon(
154+
imageVector = Icons.AutoMirrored.Default.ArrowBack,
155+
contentDescription = null
156+
)
157+
}
158+
},
159+
title = {
160+
Text(if (isDetailScreen) "Preview" else "Main")
161+
}
162+
)
163+
164+
val dragModifier = Modifier.pointerInput(Unit) {
165+
val componentWidth = size.width
166+
detectHorizontalDragGestures(
167+
onDragEnd = {
168+
coroutineScope.launch {
169+
if (offsetX.value > componentWidth / 2f) {
170+
offsetX.animateTo(
171+
componentWidth.toFloat(),
172+
animationSpec = tween(durationMillis = 300)
173+
)
174+
} else {
175+
offsetX.animateTo(
176+
0f,
177+
animationSpec = tween(durationMillis = 300)
178+
179+
)
180+
}
181+
}
182+
}
183+
) { _, dragAmount ->
184+
coroutineScope.launch {
185+
offsetX.snapTo(
186+
(offsetX.value - dragAmount).coerceIn(
187+
0f,
188+
componentWidth.toFloat()
189+
)
190+
)
191+
}
192+
}
193+
}
194+
BoxWithConstraints(
195+
modifier = Modifier.fillMaxSize()
196+
) {
197+
198+
199+
val width = constraints.maxWidth.toFloat()
200+
201+
202+
Box(
203+
modifier = Modifier
204+
.graphicsLayer {
205+
translationX = -offsetX.value
206+
}
207+
) {
208+
MainScreen {
209+
coroutineScope.launch {
210+
data = Holder(it)
211+
isDetailScreen = true
212+
offsetX.animateTo(
213+
targetValue = width,
214+
animationSpec = tween(300)
215+
)
216+
}
217+
}
218+
}
219+
220+
data?.let {
221+
if (isDetailScreen) {
222+
Box(
223+
modifier = Modifier
224+
.zIndex(100f)
225+
.then(dragModifier)
226+
.graphicsLayer {
227+
translationX = size.width - offsetX.value
228+
}
229+
) {
230+
PreviewScreen(it.value)
231+
}
232+
}
233+
}
234+
}
235+
}
236+
}
237+
}
238+
}
239+
240+
@Composable
241+
fun MainScreen(
242+
onClick: (Int) -> Unit,
243+
) {
244+
245+
Column(
246+
modifier = Modifier
247+
.fillMaxSize()
248+
.background(Color.White)
249+
) {
250+
251+
252+
LazyColumn(
253+
state = rememberLazyListState(),
254+
contentPadding = PaddingValues(vertical = 16.dp),
255+
verticalArrangement = Arrangement.spacedBy(16.dp),
256+
modifier = Modifier
257+
)
258+
{
259+
items(120) {
260+
Card(
261+
modifier = Modifier
262+
.fillMaxWidth()
263+
.clickable {
264+
onClick(it)
265+
},
266+
elevation = CardDefaults.cardElevation(4.dp),
267+
colors = CardDefaults.cardColors(Color.White)
268+
) {
269+
Row {
270+
Text(text = "Hello $it", modifier = Modifier.padding(16.dp))
271+
Spacer(modifier = Modifier.weight(1f))
272+
Text(text = "World $it", modifier = Modifier.padding(16.dp))
273+
}
274+
}
275+
}
276+
}
277+
}
278+
}
279+
280+
@Composable
281+
fun PreviewScreen(
282+
value: Int,
283+
) {
284+
285+
Box(
286+
modifier = Modifier
287+
.fillMaxSize()
288+
289+
) {
290+
Column(
291+
Modifier
292+
.fillMaxSize()
293+
.background(Color.Green)
294+
.padding(horizontal = 5.dp),
295+
verticalArrangement = Arrangement.Center,
296+
horizontalAlignment = Alignment.CenterHorizontally
297+
) {
298+
Text(text = "Hello $value", fontSize = 20.sp)
299+
}
300+
}
301+
}
302+
303+
private object Routes {
304+
const val HOME_ROUTE = "Home"
305+
const val SETTINGS_ROUTE = "Settings"
306+
}
307+
308+
data class Holder(val value: Int)

0 commit comments

Comments
 (0)