Grids with different spans lose drag focus
Hi,
I tried using the library for a horizontal grid that uses differently spanned items, and I found an issue. In my example, there is a column containing two items in two rows, and then a large item spanning both rows that's next to it. When I drag the bottom item from this column over the big item and collision is detected, the dragged item jumps instantly behind the big item. Here's a video of the bug:
https://github.com/user-attachments/assets/910c156c-5d74-4162-8ccf-9105b2f46827
Tried to play with it but haven't found a solution yet. Thanks for looking into it. Here's the code of the example:
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import sh.calvin.reorderable.ReorderableItem
import sh.calvin.reorderable.rememberReorderableLazyGridState
import java.util.UUID
@Composable
fun CalvinReorderableGrid(modifier: Modifier = Modifier) {
var list by remember {
mutableStateOf(
listOf(
GridItem(text = "Item 1", spansWholeGrid = false),
GridItem(text = "Item 2", spansWholeGrid = false),
GridItem(text = "Item 3", spansWholeGrid = true),
GridItem(text = "Item 4", spansWholeGrid = false),
GridItem(text = "Item 5", spansWholeGrid = false),
GridItem(text = "Item 6", spansWholeGrid = true),
)
)
}
val lazyGridState = rememberLazyGridState()
val reorderableLazyListState = rememberReorderableLazyGridState(lazyGridState) { from, to ->
list = list.toMutableList().apply {
add(to.index, removeAt(from.index))
}
}
LazyHorizontalGrid(
modifier = modifier.fillMaxHeight(0.25f).fillMaxWidth(),
rows = GridCells.Fixed(2),
state = lazyGridState,
contentPadding = PaddingValues(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(
items = list,
key = { it.id },
span = { if (it.spansWholeGrid) GridItemSpan(maxLineSpan) else GridItemSpan(1) }
) {
ReorderableItem(reorderableLazyListState, key = it.id) { isDragging ->
val elevation by animateDpAsState(if (isDragging) 4.dp else 0.dp)
Surface(
modifier = Modifier
.width(150.dp)
.fillMaxSize()
.border(1.dp, Color.Black)
.longPressDraggableHandle(),
shadowElevation = elevation
) {
Column {
Text(it.text, Modifier.padding(vertical = 8.dp))
}
}
}
}
}
}
data class GridItem(
val id: String = UUID.randomUUID().toString(),
val text: String,
val spansWholeGrid: Boolean = false
)
thank you for including a video!
this might be difficult to solve. I think in this case I'll have to make the final position of item 4 scroll in before moving it there, but I'm not sure if there's a way to know where item 4 will end up before it moves off screen.
the reason this happens is off screen items are not rendered and since this was initially designed with just lists in mind. there's the assumption that swapping two items means both of them will remain on screen. in this case, the items aren't really swapped since item 3 is too big to move to where item 4 was
for now if you change the scrollThreshold to more than half of the width of your items, it should hopefully help with the problem?
Yeah, it is difficult 🙈😁. I have tried to increase the threshold, which was bit wonky. I also tried to move both items in the column when doing this move, which kind of worked, but I eventually went for a custom solution to this problem... But I still have it on my mind now and thought that you might maybe find a way to do this, for others 😁