Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
SwipeToDelete is a boilerplate-free simple implementation of SwipeToDismiss for bidirectional delete gestures in Jetpack Compose.
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
/*
Usage example:
// rememberDeleteState is a wrapper for rememberDismissState.
// You only need to pass a delete callback to it and it handles the rest.
val deleteState = rememberDeleteState(
onDelete = { viewModel.remove(item) }
)
// SwipeToDelete needs only rememberDeleteState's value and the item's content to work.
SwipeToDelete(state = deleteState) {
Card(
// And you can still access the same DismissState properties to aid with animations.
elevation = animateDpAsState(
if (deleteState.dismissDirection != null) 4.dp else 0.dp
).value
) {
ItemView(item)
}
}
// Note: It uses experimental Material APIs
*/
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun rememberDeleteState(
onDelete: () -> Unit
): DismissState {
return rememberDismissState(
confirmStateChange = {
if (it == DismissValue.DismissedToStart || it == DismissValue.DismissedToEnd) {
onDelete()
true
} else false
}
)
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun SwipeToDelete(
state: DismissState,
content: @Composable RowScope.() -> Unit
) {
SwipeToDismiss(
state = state,
directions = setOf(DismissDirection.StartToEnd, DismissDirection.EndToStart),
dismissThresholds = { direction ->
FractionalThreshold(if (direction == DismissDirection.StartToEnd) 0.25f else 0.5f)
},
background = {
val direction = state.dismissDirection ?: return@SwipeToDismiss
val color by animateColorAsState(
when (state.targetValue) {
DismissValue.Default -> Color.LightGray.copy(alpha = 0.5f)
else -> Color.Red
}
)
val alignment = when (direction) {
DismissDirection.StartToEnd -> Alignment.CenterStart
DismissDirection.EndToStart -> Alignment.CenterEnd
}
val scale by animateFloatAsState(
if (state.targetValue == DismissValue.Default) 0.75f else 1f
)
Box(
Modifier
.fillMaxSize()
.background(color)
.padding(horizontal = 20.dp),
contentAlignment = alignment
) {
Icon(
Icons.Default.Delete,
contentDescription = "Remove item",
modifier = Modifier.scale(scale)
)
}
},
dismissContent = content
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment