Skip to content

Commit 5bf449b

Browse files
authored
Add Swipe to reveal examples (#412)
* Add Swipe to reveal examples * Apply Spotless * Adding swipe to dismiss examples to the top compose examples. * Tidy up merge conflict errors --------- Co-authored-by: jakeroseman <[email protected]>
1 parent e6997d9 commit 5bf449b

File tree

3 files changed

+342
-0
lines changed

3 files changed

+342
-0
lines changed

compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import com.example.compose.snippets.components.ScaffoldExample
4949
import com.example.compose.snippets.components.SearchBarExamples
5050
import com.example.compose.snippets.components.SegmentedButtonExamples
5151
import com.example.compose.snippets.components.SliderExamples
52+
import com.example.compose.snippets.components.SwipeToDismissBoxExamples
5253
import com.example.compose.snippets.components.SwitchExamples
5354
import com.example.compose.snippets.components.TimePickerExamples
5455
import com.example.compose.snippets.components.TooltipExamples
@@ -122,6 +123,7 @@ class SnippetsActivity : ComponentActivity() {
122123
TopComponentsDestination.TooltipExamples -> TooltipExamples()
123124
TopComponentsDestination.NavigationDrawerExamples -> NavigationDrawerExamples()
124125
TopComponentsDestination.SegmentedButtonExamples -> SegmentedButtonExamples()
126+
TopComponentsDestination.SwipeToDismissBoxExamples -> SwipeToDismissBoxExamples()
125127
TopComponentsDestination.SearchBarExamples -> SearchBarExamples()
126128
}
127129
}
Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
/*
2+
* Copyright 2024 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+
package com.example.compose.snippets.components
18+
19+
import androidx.compose.animation.animateColorAsState
20+
import androidx.compose.foundation.background
21+
import androidx.compose.foundation.layout.Arrangement
22+
import androidx.compose.foundation.layout.Column
23+
import androidx.compose.foundation.layout.Row
24+
import androidx.compose.foundation.layout.Spacer
25+
import androidx.compose.foundation.layout.fillMaxSize
26+
import androidx.compose.foundation.layout.padding
27+
import androidx.compose.foundation.lazy.LazyColumn
28+
import androidx.compose.foundation.lazy.items
29+
import androidx.compose.material.icons.Icons
30+
import androidx.compose.material.icons.filled.CheckBox
31+
import androidx.compose.material.icons.filled.CheckBoxOutlineBlank
32+
import androidx.compose.material.icons.filled.Delete
33+
import androidx.compose.material3.Icon
34+
import androidx.compose.material3.ListItem
35+
import androidx.compose.material3.OutlinedCard
36+
import androidx.compose.material3.SwipeToDismissBox
37+
import androidx.compose.material3.SwipeToDismissBoxValue
38+
import androidx.compose.material3.Text
39+
import androidx.compose.material3.rememberSwipeToDismissBoxState
40+
import androidx.compose.runtime.Composable
41+
import androidx.compose.runtime.getValue
42+
import androidx.compose.runtime.mutableStateListOf
43+
import androidx.compose.runtime.remember
44+
import androidx.compose.ui.Alignment
45+
import androidx.compose.ui.Modifier
46+
import androidx.compose.ui.graphics.Color
47+
import androidx.compose.ui.graphics.RectangleShape
48+
import androidx.compose.ui.graphics.lerp
49+
import androidx.compose.ui.text.font.FontWeight
50+
import androidx.compose.ui.tooling.preview.Preview
51+
import androidx.compose.ui.unit.dp
52+
53+
@Preview
54+
@Composable
55+
fun SwipeToDismissBoxExamples() {
56+
Column(
57+
modifier = Modifier
58+
.padding(16.dp)
59+
.fillMaxSize(),
60+
verticalArrangement = Arrangement.spacedBy(24.dp),
61+
horizontalAlignment = Alignment.CenterHorizontally,
62+
) {
63+
Text("Swipe to dismiss with change of background", fontWeight = FontWeight.Bold)
64+
SwipeItemExample()
65+
Text("Swipe to dismiss with a cross-fade animation", fontWeight = FontWeight.Bold)
66+
SwipeCardItemExample()
67+
}
68+
}
69+
70+
// [START android_compose_components_todoitem]
71+
data class TodoItem(
72+
var isItemDone: Boolean,
73+
var itemDescription: String
74+
)
75+
// [END android_compose_components_todoitem]
76+
77+
// [START android_compose_components_swipeitem]
78+
@Composable
79+
fun SwipeItem(
80+
value: TodoItem,
81+
startToEndAction: (TodoItem) -> Unit,
82+
endToStartAction: (TodoItem) -> Unit,
83+
modifier: Modifier = Modifier,
84+
content: @Composable (TodoItem) -> Unit
85+
) {
86+
val swipeToDismissBoxState = rememberSwipeToDismissBoxState(
87+
confirmValueChange = {
88+
when (it) {
89+
SwipeToDismissBoxValue.StartToEnd -> {
90+
startToEndAction(value)
91+
// Do not dismiss this item.
92+
false
93+
}
94+
SwipeToDismissBoxValue.EndToStart -> {
95+
endToStartAction(value)
96+
true
97+
}
98+
SwipeToDismissBoxValue.Settled -> {
99+
false
100+
}
101+
}
102+
}
103+
)
104+
105+
SwipeToDismissBox(
106+
state = swipeToDismissBoxState,
107+
modifier = modifier
108+
.fillMaxSize(),
109+
backgroundContent = {
110+
Row(
111+
modifier = Modifier
112+
.background(
113+
when (swipeToDismissBoxState.dismissDirection) {
114+
SwipeToDismissBoxValue.StartToEnd -> {
115+
Color.Blue
116+
}
117+
SwipeToDismissBoxValue.EndToStart -> {
118+
Color.Red
119+
}
120+
SwipeToDismissBoxValue.Settled -> {
121+
Color.LightGray
122+
}
123+
}
124+
)
125+
.fillMaxSize(),
126+
verticalAlignment = Alignment.CenterVertically,
127+
horizontalArrangement = Arrangement.SpaceBetween
128+
) {
129+
when (swipeToDismissBoxState.dismissDirection) {
130+
SwipeToDismissBoxValue.StartToEnd -> {
131+
if (value.isItemDone) {
132+
Icon(
133+
imageVector = Icons.Default.CheckBox,
134+
contentDescription = "Item done",
135+
tint = Color.White,
136+
modifier = Modifier
137+
.padding(12.dp)
138+
)
139+
} else {
140+
Icon(
141+
imageVector = Icons.Default.CheckBoxOutlineBlank,
142+
contentDescription = "Item not done",
143+
tint = Color.White,
144+
modifier = Modifier
145+
.padding(12.dp)
146+
)
147+
}
148+
}
149+
150+
SwipeToDismissBoxValue.EndToStart -> {
151+
Spacer(modifier = Modifier)
152+
Icon(
153+
imageVector = Icons.Default.Delete,
154+
contentDescription = "Remove item",
155+
tint = Color.White,
156+
modifier = Modifier
157+
.padding(12.dp)
158+
)
159+
}
160+
161+
SwipeToDismissBoxValue.Settled -> {}
162+
}
163+
}
164+
}
165+
) {
166+
content(value)
167+
}
168+
}
169+
// [END android_compose_components_swipeitem]
170+
171+
@Preview(showBackground = true)
172+
// [START android_compose_components_swipeitemexample]
173+
@Composable
174+
private fun SwipeItemExample() {
175+
val todoItems = remember {
176+
mutableStateListOf(
177+
TodoItem(isItemDone = false, itemDescription = "Pay bills"),
178+
TodoItem(isItemDone = false, itemDescription = "Buy groceries"),
179+
TodoItem(isItemDone = false, itemDescription = "Go to gym"),
180+
TodoItem(isItemDone = false, itemDescription = "Get dinner")
181+
)
182+
}
183+
184+
LazyColumn {
185+
items(
186+
items = todoItems,
187+
key = { it.itemDescription }
188+
) { todoItem ->
189+
SwipeItem(
190+
value = todoItem,
191+
startToEndAction = {
192+
todoItem.isItemDone = !todoItem.isItemDone
193+
},
194+
endToStartAction = {
195+
todoItems -= todoItem
196+
}
197+
) {
198+
ListItem(
199+
headlineContent = { Text(text = todoItem.itemDescription) },
200+
supportingContent = { Text(text = "swipe me to update or remove.") }
201+
)
202+
}
203+
}
204+
}
205+
}
206+
// [END android_compose_components_swipeitemexample]
207+
208+
// [START android_compose_components_swipecarditem]
209+
@Composable
210+
fun SwipeCardItem(
211+
value: TodoItem,
212+
startToEndAction: (TodoItem) -> Unit,
213+
endToStartAction: (TodoItem) -> Unit,
214+
modifier: Modifier = Modifier,
215+
content: @Composable (TodoItem) -> Unit
216+
) {
217+
val swipeToDismissState = rememberSwipeToDismissBoxState(
218+
positionalThreshold = { totalDistance -> totalDistance * 0.25f },
219+
confirmValueChange = {
220+
when (it) {
221+
SwipeToDismissBoxValue.StartToEnd -> {
222+
startToEndAction(value)
223+
// Do not dismiss this item.
224+
false
225+
}
226+
SwipeToDismissBoxValue.EndToStart -> {
227+
endToStartAction(value)
228+
true
229+
}
230+
SwipeToDismissBoxValue.Settled -> {
231+
false
232+
}
233+
}
234+
}
235+
)
236+
237+
SwipeToDismissBox(
238+
modifier = Modifier,
239+
state = swipeToDismissState,
240+
backgroundContent = {
241+
// Cross-fade the background color as the drag gesture progresses.
242+
val color by animateColorAsState(
243+
when (swipeToDismissState.targetValue) {
244+
SwipeToDismissBoxValue.Settled -> Color.LightGray
245+
SwipeToDismissBoxValue.StartToEnd ->
246+
lerp(Color.LightGray, Color.Blue, swipeToDismissState.progress)
247+
248+
SwipeToDismissBoxValue.EndToStart ->
249+
lerp(Color.LightGray, Color.Red, swipeToDismissState.progress)
250+
},
251+
label = "swipeable card item background color"
252+
)
253+
Row(
254+
modifier = Modifier
255+
.background(color)
256+
.fillMaxSize(),
257+
verticalAlignment = Alignment.CenterVertically,
258+
horizontalArrangement = Arrangement.SpaceBetween
259+
) {
260+
when (swipeToDismissState.dismissDirection) {
261+
SwipeToDismissBoxValue.StartToEnd -> {
262+
if (value.isItemDone) {
263+
Icon(
264+
imageVector = Icons.Default.CheckBox,
265+
contentDescription = "Item done",
266+
tint = Color.White,
267+
modifier = Modifier
268+
.padding(12.dp)
269+
)
270+
} else {
271+
Icon(
272+
imageVector = Icons.Default.CheckBoxOutlineBlank,
273+
contentDescription = "Item not done",
274+
tint = Color.White,
275+
modifier = Modifier
276+
.padding(12.dp)
277+
)
278+
}
279+
}
280+
281+
SwipeToDismissBoxValue.EndToStart -> {
282+
Spacer(modifier = Modifier)
283+
Icon(
284+
imageVector = Icons.Default.Delete,
285+
contentDescription = "Remove item",
286+
tint = Color.White,
287+
modifier = Modifier
288+
.padding(12.dp)
289+
)
290+
}
291+
292+
SwipeToDismissBoxValue.Settled -> {}
293+
}
294+
}
295+
}
296+
) {
297+
content(value)
298+
}
299+
}
300+
// [END android_compose_components_swipecarditem]
301+
302+
// [START android_compose_components_swipecarditemexample]
303+
@Preview
304+
@Composable
305+
private fun SwipeCardItemExample() {
306+
val todoItems = remember {
307+
mutableStateListOf(
308+
TodoItem(isItemDone = false, itemDescription = "Pay bills"),
309+
TodoItem(isItemDone = false, itemDescription = "Buy groceries"),
310+
TodoItem(isItemDone = false, itemDescription = "Go to gym"),
311+
TodoItem(isItemDone = false, itemDescription = "Get dinner")
312+
)
313+
}
314+
315+
LazyColumn {
316+
items(
317+
items = todoItems,
318+
key = { it.itemDescription }
319+
) { todoItem ->
320+
SwipeCardItem(
321+
value = todoItem,
322+
startToEndAction = {
323+
todoItem.isItemDone = !todoItem.isItemDone
324+
},
325+
endToStartAction = {
326+
todoItems -= todoItem
327+
}
328+
) {
329+
OutlinedCard(shape = RectangleShape) {
330+
ListItem(
331+
headlineContent = { Text(todoItem.itemDescription) },
332+
supportingContent = { Text("swipe me to update or remove.") }
333+
)
334+
}
335+
}
336+
}
337+
}
338+
}
339+
// [END android_compose_components_swipecarditemexample]

compose/snippets/src/main/java/com/example/compose/snippets/navigation/Destination.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,6 @@ enum class TopComponentsDestination(val route: String, val title: String) {
5050
TooltipExamples("tooltipExamples", "Tooltips"),
5151
NavigationDrawerExamples("navigationDrawerExamples", "Navigation drawer"),
5252
SegmentedButtonExamples("segmentedButtonExamples", "Segmented button"),
53+
SwipeToDismissBoxExamples("swipeToDismissBoxExamples", "Swipe to dismiss box examples"),
5354
SearchBarExamples("searchBarExamples", "Search bar")
5455
}

0 commit comments

Comments
 (0)