Skip to content

Instantly share code, notes, and snippets.

@bmc08gt
Last active December 15, 2023 03:40
Show Gist options
  • Save bmc08gt/f130c78580ccfb269795e74bcef96aa6 to your computer and use it in GitHub Desktop.
Save bmc08gt/f130c78580ccfb269795e74bcef96aa6 to your computer and use it in GitHub Desktop.
Jetpack Compose Modifier extension to implement swipe-to-delete via Modifier.draggable
import androidx.animation.IntToVectorConverter
import androidx.animation.tween
import androidx.compose.Composable
import androidx.compose.mutableStateOf
import androidx.compose.remember
import androidx.ui.animation.animatedFloat
import androidx.ui.animation.animatedValue
import androidx.ui.core.*
import androidx.ui.core.gesture.scrollorientationlocking.Orientation
import androidx.ui.foundation.animation.FlingConfig
import androidx.ui.foundation.animation.fling
import androidx.ui.foundation.gestures.draggable
import kotlin.math.absoluteValue
import kotlin.math.roundToInt
/**
* [Modifier] enabling swipe to delete functionality on a Composable view.
*
* NOTE: <b>MUST</b> be added prior to padding modifiers to ensure that the collapse animation
* will run to remove all available space.
*/
@Composable
fun Modifier.swipeToDelete(
constraints: Constraints,
threshold: Float = .8f,
swipeDirection: LayoutDirection? = null,
onDelete: () -> Unit
): Modifier {
val width = constraints.maxWidth
val draggable = remember { mutableStateOf(true) }
val deleted = remember { mutableStateOf(false) }
val positionOffset = animatedFloat(0f)
val collapse = remember { mutableStateOf(0) }
val animatedCollapse = animatedValue(initVal = 0, converter = IntToVectorConverter)
return this + Modifier.draggable(
enabled = draggable.value,
orientation = Orientation.Horizontal,
onDrag = { delta ->
when (swipeDirection) {
LayoutDirection.Ltr -> positionOffset.snapTo((positionOffset.value + delta).coerceAtLeast(0f))
LayoutDirection.Rtl -> positionOffset.snapTo((positionOffset.value + delta).coerceAtMost(0f))
else -> positionOffset.snapTo(positionOffset.value + delta)
}
},
onDragStopped = { velocity ->
val config = FlingConfig(anchors = listOf(-width.toFloat(), 0f, width.toFloat()))
if (positionOffset.value.absoluteValue >= threshold) {
positionOffset.fling(velocity, config) { _, endValue, _ ->
if (endValue != 0f) {
animatedCollapse.snapTo(collapse.value)
animatedCollapse.animateTo(0, onEnd = { _, _ ->
deleted.value = true
draggable.value = false
onDelete()
}, anim = tween(500))
}
}
} else {
draggable.value = true
}
}
) + object : LayoutModifier {
override fun MeasureScope.measure(
measurable: Measurable,
constraints: Constraints,
layoutDirection: LayoutDirection
): MeasureScope.MeasureResult {
val child = measurable.measure(constraints)
positionOffset.setBounds(-width.toFloat(), width.toFloat())
collapse.value = child.height
val placeHeight = if (animatedCollapse.isRunning || deleted.value) {
animatedCollapse.value
} else {
child.height
}
return layout(child.width, placeHeight) {
child.place(positionOffset.value.roundToInt(), 0)
}
}
}
}
@bmc08gt
Copy link
Author

bmc08gt commented Sep 20, 2020

Hi, I'm trying to use this to implement swipe to delete in a LazyColumn item.
Where can I get the constraints for this modifier?
I tried to simply use Constraints but since in this case constraints.maxWidth is Constraints.Infinity, onDelete() is not trigger at all.
Setting a fix number using Constraints.fixedWidth() works but I want to make it fill the available width.

I would definitely try out https://gist.github.com/bmc08gt/fca95db3bf9fcf255d76f03ec10ea3f9. This gist was created before there was a first class modifier for this available and that one has better support.

@anhtuan23
Copy link

Thank you. Using SwipeToDismiss is definitely easier.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment