I am trying to implement a list of items with the ability to delete items with swipe. I've basically used LazyColumn
and SwipeToDismiss
composables.
With default configuration vertical scrolling of LazyColumn and horizontal swiping of SwipeToDismiss
mixes and I mostly trigger SwipeToDismiss while trying to do verticall scrolling.
To be able to change sensitivity of SwipeToDismiss
I've looked how touch system works. As far as I understand children composables should ask to parents before consuming dragging events and parents can consume before children by utilizing nestedScroll. But there is a problem with SwipeToDismiss
, it does not dispatch drag events to parents if the dragging angle on the x&y axis is between 0 & 45 degrees. So parents are not able to consume dragging events.
The problem is reproducable by changing the example on the nestedScroll documentation page. Just replace the items with SwipeToDismiss
. You can also find the changed example source code below.
I am trying to understand if this is this a bug, or am I missing something and how can I make SwipeToDismiss
and vertical scrolling work together better.
// here we use LazyColumn that has build-in nested scroll, but we want to act like a
// parent for this LazyColumn and participate in its nested scroll.
// Let's make a collapsing toolbar for LazyColumn
val toolbarHeight = 48.dp
val toolbarHeightPx = with(LocalDensity.current) { toolbarHeight.roundToPx().toFloat() }
// our offset to collapse toolbar
val toolbarOffsetHeightPx =
remember { mutableStateOf(0f) }
// now, let's create connection to the nested scroll system and listen to the scroll
// happening inside child LazyColumn
val nestedScrollConnection = remember {
object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
// try to consume before LazyColumn to collapse toolbar if needed, hence pre-scroll
val delta = available.y
val newOffset = toolbarOffsetHeightPx.value + delta
toolbarOffsetHeightPx.value = newOffset.coerceIn(-toolbarHeightPx, 0f)
// here's the catch: let's pretend we consumed 0 in any case, since we want
// LazyColumn to scroll anyway for good UX
// We're basically watching scroll without taking it
return Offset.Zero
}
}
}
Box(
Modifier
.fillMaxSize()
// attach as a parent to the nested scroll system
.nestedScroll(nestedScrollConnection)
) {
// our list with build in nested scroll support that will notify us about its scroll
LazyColumn(contentPadding = PaddingValues(top = toolbarHeight)) {
items(100) { index ->
val state = remember { DismissState(DismissValue.Default) { true } }
SwipeToDismiss(state = state, background = {
Text(text = "Background", modifier = Modifier.fillMaxWidth().background(color = androidx.compose.ui.graphics.Color.Red))
}) {
Text("I'm item $index", modifier = Modifier
.fillMaxWidth()
.background(color = Color.Black)
.padding(16.dp))
}
}
}
TopAppBar(
modifier = Modifier
.height(toolbarHeight)
.offset { IntOffset(x = 0, y = toolbarOffsetHeightPx.value.roundToInt()) },
title = { Text("toolbar offset is ${toolbarOffsetHeightPx.value}") }
)
}