-
-
Save virendersran01/9185a251654d09ee4d9dae2ec45d99fa to your computer and use it in GitHub Desktop.
Drag-n-Drop implementation in Jetpack Compose
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Composable | |
fun ReorderableList( | |
items: List<ReorderItem>, | |
onMove: (Int, Int) -> Unit, | |
modifier: Modifier = Modifier | |
) { | |
val scope = rememberCoroutineScope() | |
var overscrollJob by remember { mutableStateOf<Job?>(null) } | |
val reorderableListState = rememberReorderableListState(onMove = onMove) | |
LazyColumn( | |
modifier = modifier | |
.pointerInput(Unit) { | |
detectDragGesturesAfterLongPress( | |
onDrag = { change, offset -> | |
change.consumeAllChanges() | |
reorderableListState.onDrag(offset) | |
if (overscrollJob?.isActive == true) | |
return@detectDragGesturesAfterLongPress | |
reorderableListState.checkForOverScroll() | |
.takeIf { it != 0f } | |
?.let { overscrollJob = scope.launch { reorderableListState.lazyListState.scrollBy(it) } } | |
?: run { overscrollJob?.cancel() } | |
}, | |
onDragStart = { offset -> reorderableListState.onDragStart(offset) }, | |
onDragEnd = { reorderableListState.onDragInterrupted() }, | |
onDragCancel = { reorderableListState.onDragInterrupted() } | |
) | |
}, | |
state = reorderableListState.lazyListState | |
) { | |
itemsIndexed(items) { index, item -> | |
Column( | |
modifier = Modifier | |
.composed { | |
val offsetOrNull = | |
reorderableListState.elementDisplacement.takeIf { | |
index == reorderableListState.currentIndexOfDraggedItem | |
} | |
Modifier | |
.graphicsLayer { | |
translationY = offsetOrNull ?: 0f | |
} | |
} | |
.background(Color.White, shape = RoundedCornerShape(4.dp)) | |
.fillMaxWidth() | |
) { Text(text = "Item ${item.id}") } | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
LazyListItemInfo.index is the item's absolute index in the list | |
Based on the item's "relative position" with the "currently top" visible item, | |
this returns LazyListItemInfo corresponding to it | |
*/ | |
fun LazyListState.getVisibleItemInfoFor(absoluteIndex: Int): LazyListItemInfo? { | |
return this.layoutInfo.visibleItemsInfo.getOrNull(absoluteIndex - this.layoutInfo.visibleItemsInfo.first().index) | |
} | |
/* | |
Bottom offset of the element in Vertical list | |
*/ | |
val LazyListItemInfo.offsetEnd: Int | |
get() = this.offset + this.size | |
/* | |
Moving element in the list | |
*/ | |
fun <T> MutableList<T>.move(from: Int, to: Int) { | |
if (from == to) | |
return | |
val element = this.removeAt(from) ?: return | |
this.add(to, element) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Composable | |
fun rememberReorderableListState( | |
lazyListState: LazyListState = rememberLazyListState(), | |
onMove: (Int, Int) -> Unit, | |
): ReorderableListState { | |
return remember { ReorderableListState(lazyListState = lazyListState, onMove = onMove) } | |
} | |
class ReorderableListState( | |
val lazyListState: LazyListState, | |
private val onMove: (Int, Int) -> Unit | |
) { | |
var draggedDistance by mutableStateOf(0f) | |
// used to obtain initial offsets on drag start | |
var initiallyDraggedElement by mutableStateOf<LazyListItemInfo?>(null) | |
var currentIndexOfDraggedItem by mutableStateOf<Int?>(null) | |
val initialOffsets: Pair<Int, Int>? | |
get() = initiallyDraggedElement?.let { Pair(it.offset, it.offsetEnd) } | |
val elementDisplacement: Float? | |
get() = currentIndexOfDraggedItem | |
?.let { lazyListState.getVisibleItemInfoFor(absoluteIndex = it) } | |
?.let { item -> (initiallyDraggedElement?.offset ?: 0f).toFloat() + draggedDistance - item.offset } | |
val currentElement: LazyListItemInfo? | |
get() = currentIndexOfDraggedItem?.let { | |
lazyListState.getVisibleItemInfoFor(absoluteIndex = it) | |
} | |
var overscrollJob by mutableStateOf<Job?>(null) | |
fun onDragStart(offset: Offset) { | |
lazyListState.layoutInfo.visibleItemsInfo | |
.firstOrNull { item -> offset.y.toInt() in item.offset..(item.offset + item.size) } | |
?.also { | |
currentIndexOfDraggedItem = it.index | |
initiallyDraggedElement = it | |
} | |
} | |
fun onDragInterrupted() { | |
draggedDistance = 0f | |
currentIndexOfDraggedItem = null | |
initiallyDraggedElement = null | |
overscrollJob?.cancel() | |
} | |
fun onDrag(offset: Offset) { | |
draggedDistance += offset.y | |
initialOffsets?.let { (topOffset, bottomOffset) -> | |
val startOffset = topOffset + draggedDistance | |
val endOffset = bottomOffset + draggedDistance | |
currentElement?.let { hovered -> | |
lazyListState.layoutInfo.visibleItemsInfo | |
.filterNot { item -> item.offsetEnd < startOffset || item.offset > endOffset || hovered.index == item.index } | |
.firstOrNull { item -> | |
val delta = startOffset - hovered.offset | |
when { | |
delta > 0 -> (endOffset > item.offsetEnd) | |
else -> (startOffset < item.offset) | |
} | |
} | |
?.also { item -> | |
currentIndexOfDraggedItem?.let { current -> onMove.invoke(current, item.index) } | |
currentIndexOfDraggedItem = item.index | |
} | |
} | |
} | |
} | |
fun checkForOverScroll(): Float { | |
return initiallyDraggedElement?.let { | |
val startOffset = it.offset + draggedDistance | |
val endOffset = it.offsetEnd + draggedDistance | |
return@let when { | |
draggedDistance > 0 -> (endOffset - lazyListState.layoutInfo.viewportEndOffset).takeIf { diff -> diff > 0 } | |
draggedDistance < 0 -> (startOffset - lazyListState.layoutInfo.viewportStartOffset).takeIf { diff -> diff < 0 } | |
else -> null | |
} | |
} ?: 0f | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Can you show us ? the data class of the ReorderItem