Skip to content

Commit

Permalink
Add Swipe to reveal examples (#412)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
jakeroseman and jakeroseman authored Dec 20, 2024
1 parent e6997d9 commit 5bf449b
Show file tree
Hide file tree
Showing 3 changed files with 342 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import com.example.compose.snippets.components.ScaffoldExample
import com.example.compose.snippets.components.SearchBarExamples
import com.example.compose.snippets.components.SegmentedButtonExamples
import com.example.compose.snippets.components.SliderExamples
import com.example.compose.snippets.components.SwipeToDismissBoxExamples
import com.example.compose.snippets.components.SwitchExamples
import com.example.compose.snippets.components.TimePickerExamples
import com.example.compose.snippets.components.TooltipExamples
Expand Down Expand Up @@ -122,6 +123,7 @@ class SnippetsActivity : ComponentActivity() {
TopComponentsDestination.TooltipExamples -> TooltipExamples()
TopComponentsDestination.NavigationDrawerExamples -> NavigationDrawerExamples()
TopComponentsDestination.SegmentedButtonExamples -> SegmentedButtonExamples()
TopComponentsDestination.SwipeToDismissBoxExamples -> SwipeToDismissBoxExamples()
TopComponentsDestination.SearchBarExamples -> SearchBarExamples()
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,339 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.compose.snippets.components

import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CheckBox
import androidx.compose.material.icons.filled.CheckBoxOutlineBlank
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.SwipeToDismissBox
import androidx.compose.material3.SwipeToDismissBoxValue
import androidx.compose.material3.Text
import androidx.compose.material3.rememberSwipeToDismissBoxState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp

@Preview
@Composable
fun SwipeToDismissBoxExamples() {
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text("Swipe to dismiss with change of background", fontWeight = FontWeight.Bold)
SwipeItemExample()
Text("Swipe to dismiss with a cross-fade animation", fontWeight = FontWeight.Bold)
SwipeCardItemExample()
}
}

// [START android_compose_components_todoitem]
data class TodoItem(
var isItemDone: Boolean,
var itemDescription: String
)
// [END android_compose_components_todoitem]

// [START android_compose_components_swipeitem]
@Composable
fun SwipeItem(
value: TodoItem,
startToEndAction: (TodoItem) -> Unit,
endToStartAction: (TodoItem) -> Unit,
modifier: Modifier = Modifier,
content: @Composable (TodoItem) -> Unit
) {
val swipeToDismissBoxState = rememberSwipeToDismissBoxState(
confirmValueChange = {
when (it) {
SwipeToDismissBoxValue.StartToEnd -> {
startToEndAction(value)
// Do not dismiss this item.
false
}
SwipeToDismissBoxValue.EndToStart -> {
endToStartAction(value)
true
}
SwipeToDismissBoxValue.Settled -> {
false
}
}
}
)

SwipeToDismissBox(
state = swipeToDismissBoxState,
modifier = modifier
.fillMaxSize(),
backgroundContent = {
Row(
modifier = Modifier
.background(
when (swipeToDismissBoxState.dismissDirection) {
SwipeToDismissBoxValue.StartToEnd -> {
Color.Blue
}
SwipeToDismissBoxValue.EndToStart -> {
Color.Red
}
SwipeToDismissBoxValue.Settled -> {
Color.LightGray
}
}
)
.fillMaxSize(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
when (swipeToDismissBoxState.dismissDirection) {
SwipeToDismissBoxValue.StartToEnd -> {
if (value.isItemDone) {
Icon(
imageVector = Icons.Default.CheckBox,
contentDescription = "Item done",
tint = Color.White,
modifier = Modifier
.padding(12.dp)
)
} else {
Icon(
imageVector = Icons.Default.CheckBoxOutlineBlank,
contentDescription = "Item not done",
tint = Color.White,
modifier = Modifier
.padding(12.dp)
)
}
}

SwipeToDismissBoxValue.EndToStart -> {
Spacer(modifier = Modifier)
Icon(
imageVector = Icons.Default.Delete,
contentDescription = "Remove item",
tint = Color.White,
modifier = Modifier
.padding(12.dp)
)
}

SwipeToDismissBoxValue.Settled -> {}
}
}
}
) {
content(value)
}
}
// [END android_compose_components_swipeitem]

@Preview(showBackground = true)
// [START android_compose_components_swipeitemexample]
@Composable
private fun SwipeItemExample() {
val todoItems = remember {
mutableStateListOf(
TodoItem(isItemDone = false, itemDescription = "Pay bills"),
TodoItem(isItemDone = false, itemDescription = "Buy groceries"),
TodoItem(isItemDone = false, itemDescription = "Go to gym"),
TodoItem(isItemDone = false, itemDescription = "Get dinner")
)
}

LazyColumn {
items(
items = todoItems,
key = { it.itemDescription }
) { todoItem ->
SwipeItem(
value = todoItem,
startToEndAction = {
todoItem.isItemDone = !todoItem.isItemDone
},
endToStartAction = {
todoItems -= todoItem
}
) {
ListItem(
headlineContent = { Text(text = todoItem.itemDescription) },
supportingContent = { Text(text = "swipe me to update or remove.") }
)
}
}
}
}
// [END android_compose_components_swipeitemexample]

// [START android_compose_components_swipecarditem]
@Composable
fun SwipeCardItem(
value: TodoItem,
startToEndAction: (TodoItem) -> Unit,
endToStartAction: (TodoItem) -> Unit,
modifier: Modifier = Modifier,
content: @Composable (TodoItem) -> Unit
) {
val swipeToDismissState = rememberSwipeToDismissBoxState(
positionalThreshold = { totalDistance -> totalDistance * 0.25f },
confirmValueChange = {
when (it) {
SwipeToDismissBoxValue.StartToEnd -> {
startToEndAction(value)
// Do not dismiss this item.
false
}
SwipeToDismissBoxValue.EndToStart -> {
endToStartAction(value)
true
}
SwipeToDismissBoxValue.Settled -> {
false
}
}
}
)

SwipeToDismissBox(
modifier = Modifier,
state = swipeToDismissState,
backgroundContent = {
// Cross-fade the background color as the drag gesture progresses.
val color by animateColorAsState(
when (swipeToDismissState.targetValue) {
SwipeToDismissBoxValue.Settled -> Color.LightGray
SwipeToDismissBoxValue.StartToEnd ->
lerp(Color.LightGray, Color.Blue, swipeToDismissState.progress)

SwipeToDismissBoxValue.EndToStart ->
lerp(Color.LightGray, Color.Red, swipeToDismissState.progress)
},
label = "swipeable card item background color"
)
Row(
modifier = Modifier
.background(color)
.fillMaxSize(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
when (swipeToDismissState.dismissDirection) {
SwipeToDismissBoxValue.StartToEnd -> {
if (value.isItemDone) {
Icon(
imageVector = Icons.Default.CheckBox,
contentDescription = "Item done",
tint = Color.White,
modifier = Modifier
.padding(12.dp)
)
} else {
Icon(
imageVector = Icons.Default.CheckBoxOutlineBlank,
contentDescription = "Item not done",
tint = Color.White,
modifier = Modifier
.padding(12.dp)
)
}
}

SwipeToDismissBoxValue.EndToStart -> {
Spacer(modifier = Modifier)
Icon(
imageVector = Icons.Default.Delete,
contentDescription = "Remove item",
tint = Color.White,
modifier = Modifier
.padding(12.dp)
)
}

SwipeToDismissBoxValue.Settled -> {}
}
}
}
) {
content(value)
}
}
// [END android_compose_components_swipecarditem]

// [START android_compose_components_swipecarditemexample]
@Preview
@Composable
private fun SwipeCardItemExample() {
val todoItems = remember {
mutableStateListOf(
TodoItem(isItemDone = false, itemDescription = "Pay bills"),
TodoItem(isItemDone = false, itemDescription = "Buy groceries"),
TodoItem(isItemDone = false, itemDescription = "Go to gym"),
TodoItem(isItemDone = false, itemDescription = "Get dinner")
)
}

LazyColumn {
items(
items = todoItems,
key = { it.itemDescription }
) { todoItem ->
SwipeCardItem(
value = todoItem,
startToEndAction = {
todoItem.isItemDone = !todoItem.isItemDone
},
endToStartAction = {
todoItems -= todoItem
}
) {
OutlinedCard(shape = RectangleShape) {
ListItem(
headlineContent = { Text(todoItem.itemDescription) },
supportingContent = { Text("swipe me to update or remove.") }
)
}
}
}
}
}
// [END android_compose_components_swipecarditemexample]
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,6 @@ enum class TopComponentsDestination(val route: String, val title: String) {
TooltipExamples("tooltipExamples", "Tooltips"),
NavigationDrawerExamples("navigationDrawerExamples", "Navigation drawer"),
SegmentedButtonExamples("segmentedButtonExamples", "Segmented button"),
SwipeToDismissBoxExamples("swipeToDismissBoxExamples", "Swipe to dismiss box examples"),
SearchBarExamples("searchBarExamples", "Search bar")
}

0 comments on commit 5bf449b

Please sign in to comment.