Swipe to Delete with Jetpack Compose

Reyhan Kirci
3 min readAug 26, 2024

--

In this article, we will look into “how to add a swipe-to-delete feature to any list with Anchored Draggable.” in Jetpack Compose.

AnchoredDraggable enables the creation of draggable components with anchored states. Components can be dragged either horizontally or vertically with predefined anchor values.

Let’s get started. We will try to delete with swipe an item from the contact list.

First, let's learn how to move a component 100 pixels to the right horizontally.

We need to define an anchor condition in the following way.

  val positionalThresholds: (totalDistance: Float) -> Float =
{ totalDistance -> totalDistance * 0.5f }
val velocityThreshold: () -> Float = { with(density) { 100.dp.toPx() } }

val state = remember {
AnchoredDraggableState(
initialValue = DragAnchors.Start,
positionalThresholds,
velocityThreshold,
animationSpec = tween()
).apply {
val newAnchors = with(density) {
DraggableAnchors {
DragAnchors.Start at 0.dp.toPx()
DragAnchors.End at 100.toPx()
}
}
updateAnchors(newAnchors)
}
}
  enum class DragAnchors {
Start,
End,
}

I've positioned 2 anchors to mark the drag points.

initialValue: Parameter that specifies the initial view.

positionalThresholds: Based on the recorded distance, it determines which anchor will be snapped when we stop scrolling.

velocityThreshold: If the drag velocity exceeds the given value, it moves to the next anchor regardless of position thresholds.

animationSpec: tween() -> Determine how to animate.

updateAnchors(newAnchors): Use this to set anchors.

Finally, the offset is set by state, and then we apply the anchoredDraggable modifier to the component.

Column(
modifier = Modifier
.background(Color.DarkGray)
.fillMaxSize()
.offset {
IntOffset(
state.requireOffset().roundToInt(), 0
)
}
.anchoredDraggable(state, Orientation.Horizontal)
) {
Box(
modifier = Modifier
.size(64.dp)
.background(Color.White)
)
}

After this simple example, let's create a custom component for the swipe-to-delete feature, which I call MAnchoredDraggableBox.

@Composable
fun MAnchoredDraggableBox(
modifier: Modifier,
firstContent: @Composable (modifier: Modifier) -> Unit,
secondContent: @Composable (modifier: Modifier) -> Unit,
offsetSize: Dp
) {
//...content...//
}

firstContent : Composable component to display first

secondContent : Composable component that will be displayed after being swiped.

offsetSize : It determines how much the content’s position will change when we swipe.

@Composable
fun MAnchoredDraggableBox(
modifier: Modifier,
firstContent: @Composable (modifier: Modifier) -> Unit,
secondContent: @Composable (modifier: Modifier) -> Unit,
offsetSize: Dp
) {
val density = LocalDensity.current
val positionalThresholds: (totalDistance: Float) -> Float =
{ totalDistance -> totalDistance * 0.5f }
val velocityThreshold: () -> Float = { with(density) { 100.dp.toPx() } }

val state = remember {
AnchoredDraggableState(
initialValue = DragAnchors.Start,
positionalThresholds,
velocityThreshold,
animationSpec = tween()
).apply {
val newAnchors = with(density) {
DraggableAnchors {
DragAnchors.Start at 0.dp.toPx()
DragAnchors.End at -offsetSize.toPx()
}
}
updateAnchors(newAnchors)
}
}

Box(
modifier = modifier
) {
firstContent(
Modifier
.fillMaxWidth()
.offset {
IntOffset(
state.requireOffset().roundToInt(), 0)
}
.anchoredDraggable(state, Orientation.Horizontal)
)
secondContent(
Modifier
.align(Alignment.CenterEnd)
.offset {
IntOffset(
(state.requireOffset() + offsetSize.toPx()).roundToInt(), 0
)
}
.anchoredDraggable(state, Orientation.Horizontal)
)
}
}

state.requireOffset() + offsetSize.toPx() -> This will specify the position of the second content in relation to the first content.

We can use MAnchoredDraggableBox as follows.

val deleteComponentWidthSize = 100.dp
MAnchoredDraggableBox(
modifier = Modifier.fillMaxWidth(),
firstContent = { modifier ->
MContactItem(
modifier = modifier.background(Color.DarkGray),
item = contact
)
},
secondContent = { modifier ->
MContentItemDelete(
modifier = modifier.width(deleteComponentWidthSize),
onClick = {
onClickDelete.invoke(contact)
})
},
offsetSize = deleteComponentWidthSize
)

MContactItem, MContentItemDelete -> Composable components.

If the offsetSize value is set as the width of the second content, it will initially be hidden from view but will become fully visible after swiping.

For more jetpack compose sample app in github.

Thanks for reading! Happy coding! 🦋

--

--