Status Update
Comments
an...@google.com <an...@google.com>
il...@gmail.com <il...@gmail.com> #2
Over to Ralston to take a look. I imagine with the new relocation logic it should be fixed?
an...@google.com <an...@google.com> #3
What is happening here is that the TextField does not know that it is in a scrollable container, and since the keyboard is going to hide the currently focused text, the text field calls View.requestRectangleOnScreen which causes the entire app to pan up, and that clips the top bar.
The Relocation APIs are experimental right now. It is not used in TextField as we are past the alpha stage and can only use stable APIs in TextField. So this bug can only be fixed post 1.0
il...@gmail.com <il...@gmail.com> #4
This should be fixed by
I verified that this sample code now works when soft input mode is AdjustResize.
an...@google.com <an...@google.com> #5
I am not sure I can fix your solution as I can't compile it, run and play with it. It requires a lot of other dependencies which you didn't provide. I guess your project is not open sourced, right?
What you can try is to use this key api:
instead of
itemsIndexed(categories) { index, category ->
key(category) {
...
}
}
as using just regular key() inside LazyColumn is not doing the thing you expect because of subcompositions. With passing keys directly to itemsIndexed() the state of the composable will move correctly with the item when it is moved/swapped.
But actually I would suggest to use RecyclerView instead for now for such use case, it will work better. You can still have ComposeView as items
il...@gmail.com <il...@gmail.com> #6
Totally understandable. And thank you for your guidance!
-
The
https://developer.android.com/jetpack/compose/lists#item-keys turned out to be extremely helpful and might solve my #1 Problem - the drag gesture being canceled after recomposition. I'll try it out and let you know if I have a decent enough solution using this approach. -
I also tried a RecyclerView implementation in which the drag reorder works nicely but it has performance issues and is unstable - crashes randomly with an internal Compose error. Here's how I implemented it:
- Compose Column is situated in a custom Compose Viewpager (similar to the one in Google's Compose Jetcaster sample)
- Inside the Column - AndroidView() wrapping RecyclerView with standard ItemTouchHelper
- RecyclerView uses ComposeView as items.
Also with this solution, the scroll vs swipe gestures seems to kinda buggy. If using a RecyclerView is the best way to implement this at the moment, if that's okay with you I can show you the code and full crash log + I'll try to pin down when exactly it crashes.
Again, I appreciate your help and I think it would help me implement a proper working drag reorder in Compose which if it works well I'll most likely create a Gist for all Android devs to benefit from.
Problem #2 - my only stopper left to implement Compose drag reorder
When using:
.pointerInput(Unit) {
detectDragGesturesAfterLongPress(
...
)
}
When dragging an item downwards the LazyColum scroll gesture is still intercept and the column scrolls to the top. Resulting in: you moving an item to the bottom but your LazyColumn keeps scrolling to the top. Is there a way I can prevent the LazyColumn from scrolling when detectDragGesturesAfterLongPress() is in progress or at least hack to scroll in the correct direction?
Additional Questions
- Does Jetpack Compose ever plan to support drag reorder via long click?
- If so when is it expected to be released in alpha? Would that happen in the next 5-6 months?
Thanks, Iliyan
an...@google.com <an...@google.com> #7
You can try this solution for now for disabling scrolling:
It is not yet planned so I don't know yet. I would say in the next 6 months we will not have an official solution
ja...@vitruvian.me <ja...@vitruvian.me> #8
I think the most feasible approach for now is to continue using a RecyclerView with ItemTouchHelper. Unfortunately that also has some issues when running inside a AndroidView. I've made a new report here:
da...@gmail.com <da...@gmail.com> #9
an...@gmail.com <an...@gmail.com> #10
fr...@gmail.com <fr...@gmail.com> #11
The workaround pastebins are gone. Any workaround solution for now?
an...@gmail.com <an...@gmail.com> #12
sh...@gmail.com <sh...@gmail.com> #13
Any update on this
an...@google.com <an...@google.com> #14
Note that there is a know issue that when the first visible item is being reordered the animation is not ideal, there is a conflict between this logic and the behaviour where we maintain the scroll position based on the key. I will think how to better handle it in future
an...@google.com <an...@google.com> #15
kw...@tomtom.com <kw...@tomtom.com> #16
One suggested improvement would be to expose the onDragStart and onDragEnd callbacks from the dragContainer, so that the view model takes proper action accordingly
fun Modifier.dragContainer(dragDropState: DragDropState): Modifier {
return pointerInput(dragDropState) {
detectDragGesturesAfterLongPress(
onDrag = { change, offset ->
change.consumeAllChanges()
dragDropState.onDrag(offset = offset)
},
onDragStart = { offset -> dragDropState.onDragStarted(offset) },
onDragEnd = { dragDropState.onDragEnded() },
onDragCancel = { dragDropState.onDragInterrupted() }
)
}
}
class DragDropState internal constructor(
private val state: LazyListState,
private val scope: CoroutineScope,
private val onDragStart: (Int) -> Unit = {},
private val onDragEnd: () -> Unit = {},
private val onMove: (Int, Int) -> Unit
)
fun rememberDragDropState(
lazyListState: LazyListState,
onDragStart: (Int) -> Unit = {},
onDragEnd: () -> Unit = {},
onMove: (Int, Int) -> Unit
): DragDropState
internal fun onDragEnded() {
onDragInterrupted()
onDragEnd()
}
an...@gmail.com <an...@gmail.com> #17
fun Modifier.gridDragContainer(
dragDropState: GridDragDropState,
onDrag: ((PointerInputChange, Offset) -> Unit)? = null,
onDragStart: ((Offset) -> Unit)? = null,
onDragEnd: (() -> Unit)? = null,
onDragCancel: (() -> Unit)? = null
): Modifier {
return pointerInput(dragDropState) {
detectDragGesturesAfterLongPress(
onDrag = { change, offset ->
change.consumeAllChanges()
dragDropState.onDrag(offset = offset)
if (onDrag != null) {
onDrag(change, offset)
}
},
onDragStart = { offset ->
dragDropState.onDragStart(offset)
if (onDragStart != null) {
onDragStart(offset)
}
},
onDragEnd = {
dragDropState.onDragInterrupted()
if (onDragEnd != null) {
onDragEnd()
}
},
onDragCancel = {
dragDropState.onDragInterrupted()
if (onDragCancel != null) {
onDragCancel()
}
}
)
}
}
Then I use it like this:
Modifier.gridDragContainer(
dragDropState = dragDropState,
onDragEnd = { onDragEnd() }
)
Your way would make it so the functionality follows the drag state and I like that as well. Good job!
[Deleted User] <[Deleted User]> #18
Hi google, please implement this ASAP! Thanks.
fr...@gmail.com <fr...@gmail.com> #19
an...@google.com <an...@google.com> #20
No changes to the implementation is needed when you upgrade to Compose 1.2
th...@gmail.com <th...@gmail.com> #21
Thanks for providing an implementation for drag and drop. It is working great for most of the lists and grids in my app. However I do have some feedback. I noticed that when using a LazyColumn with items of different heights, there are some issues. When trying to drag an item it glitches and dragging does not work as expected. Any chance this could be improved? I attached a screenrecording which shows the issue.
na...@gmail.com <na...@gmail.com> #22
I also noticed some minor issues when the list items have different height. Andrey, can you check, when you have some free time?
[Deleted User] <[Deleted User]> #23
cv...@iheart.com <cv...@iheart.com> #24
an...@gmail.com <an...@gmail.com> #25
ko...@gmail.com <ko...@gmail.com> #26
Is anyone interested in a not-so-nice but working workaround using reflection or is it just considered too bad? I mean, this feature could not be implemented in 2+ years (and counting), so fighting fire with fire I guess.
di...@gmail.com <di...@gmail.com> #27
though , I am planning to make a re-orderable list... where - there's grid items in another column list. ( Groups and items in them )
Using lists within another list has been a taboo with recycler views XD , but reflections were useful to achieve this , but.... I've come to compose to try it out in a better way now.
an...@google.com <an...@google.com> #28
Both demos were updated to start using this new api, with that the previously known issue related to reordering first visible item is fixed. See the latest code for the demos:
Lists:
Grids:
Although those demos prove that it is not hard to achieve the requested behavior we will keep this bug open for now in order to asses if we want to introduce additional api making this use case even easier to code.
Or to make it easier to support more complex use cases, like the one described in
an...@google.com <an...@google.com>
ly...@gmail.com <ly...@gmail.com> #29
ly...@gmail.com <ly...@gmail.com> #30
Lists:
https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/LazyColumnDragAndDropDemo.kt
Bug: onDragEnd, onDragCancel never called if touch event move very fast. Probably current gesture was canceled.
FR: slow down the ordering. ( val slowDown = 4 )
if (overscroll != 0f) {
scrollChannel.trySend(overscroll / slowDown)
}
FR: drag by current index. ( update latest index with remember { mutableIntStateOf(0) })
internal fun onDragStartAtItem(index: Int) {
state.layoutInfo.visibleItemsInfo
.firstOrNull { it.index == index }
?.also {
onDragStart?.invoke() // onDragStart callback
draggingItemIndex = it.index
draggingItemInitialOffset = it.offset
}
}
le...@google.com <le...@google.com> #31
We're closing this ticket since most of the use cases described here are already possible with the current API. We will use
kr...@gmail.com <kr...@gmail.com> #32
Unfortunately, with the latest Compose 1.7.0 update, drag and drop feature as implemented in LazyColumnDragAndDropDemo
stopped working in isDragging
parameter instantly turns false
when trying to move around the list item.
After some investigation and lots of transitive dependencies' comparisons via ./gradlew :app:dependencies
, I suspect the regression happened between 1.7.0-beta06
and 1.7.0-beta07
for androidx.compose.ui
/ androidx.compose.animation
because (probably) that's the update where internally it started using a transitive dependency of androidx.compose.foundation:foundation-*
in 1.7.0-...
version, not 1.6.8
(like in beta06
).
Digging into androidx.compose.foundation:foundation-*
, it's probably either 1.7.0-alpha02
or 1.7.0-alpha01
that breaks the feature. That's my guess because 1.7.0-alpha01
crashes app at launch on my Pixel.
Has anyone else encountered the same issue?
Reproducible sample: in my project, update/compose-1-7-0
On master
where Compose stays at 1.6.8
, it works fine.
ne...@westnordost.de <ne...@westnordost.de> #33
I think it is probably more effective to post this as a new issue in 1.7.0 rather than in a closed one. That said, I also copied the mentioned demo verbatim into my project that uses 1.7.0 now (more specifically, androidx.compose:compose-bom:2024.09.01
) to try it out and for me, it works fine.
So, I can't reproduce the issue.
Have you tried using the BOM to refer to the compose versions? Maybe there is some incompatible dependency. Or, did you try out copying the latest version of that demo verbatim into your project and see if it works then?
kr...@gmail.com <kr...@gmail.com> #34
Yes, I already posted it as well in a follow-up drag-n-drop issue ticket.
I agree that demo samples work fine, when each list item element is plain Text
Composable. I concluded the issue occurs when list item element has TextField
inside. There's probably some bug with parent-child focus relationship but I managed to rewrite the feature to not drag-n-drop via pressing on TextField
.
Description
Hi,
Are you planning to add support item reorder via drag gesture similar to RecclerView's ItemTouchHelper#SimpleCallback? And when?
In the meantime, any guidance on how we can implement such behavior efficiently with the current APIs would be highly appreciated. (I manage to implement it with tons of workarounds)