Чтобы реализовать эту функцию, я обратился к статье Сураджа Сау.
Статья отличная, но она немного отличается от того, что я хочу сделать.
В статье представлен способ реализации DragAndDrop путем перетаскивания всей области элемента, но я хочу сделайте это, перетащив определенную область внутри элемента.
Я вам покажу.
Вы можете увидеть кнопку-гамбургер. Это тот момент, который я хочу перетащить.
Поэтому я установил Modifier.pointerInput(Unit) на кнопке гамбургера, как показано ниже.
fun DragDropList(
items: List,
onMove: (Int, Int) -> Unit,
modifier: Modifier = Modifier
) {
val scope = rememberCoroutineScope()
var overscrollJob by remember { mutableStateOf(null) }
val dragDropListState = rememberDragDropListState(onMove = onMove)
LazyColumn(
modifier = modifier,
state = dragDropListState.lazyListState,
) {
itemsIndexed(
items = items,
key = { _, item -> item.id }
) { index, item ->
val isCurrentItem = index == dragDropListState.currentIndexOfDraggedItem
val draggingModifier = if (isCurrentItem) {
Modifier
.zIndex(1f)
.graphicsLayer {
translationY = dragDropListState.elementDisplacement ?: 0f
}
} else {
Modifier
.zIndex(0f)
.animateItemPlacement(
animationSpec = tween(
durationMillis = 300,
)
)
}
Row(
modifier = draggingModifier
.background(Color.White, shape = RoundedCornerShape(4.dp))
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
) {
Image(
modifier = Modifier.pointerInput(Unit) {
detectDragGesturesImmediately(
onDragStart = { offset ->
dragDropListState.onDragStart(offset)
},
onDrag = { change, offset ->
change.consume()
dragDropListState.onDrag(offset)
if (overscrollJob?.isActive == true)
return@detectDragGesturesImmediately
dragDropListState.checkForOverScroll()
.takeIf { it != 0f }
?.let {
overscrollJob =
scope.launch { dragDropListState.lazyListState.scrollBy(it) }
}
?: run { overscrollJob?.cancel() }
},
onDragEnd = { dragDropListState.onDragInterrupted() },
onDragCancel = { dragDropListState.onDragInterrupted() }
)
},
imageVector = ImageVector.vectorResource(id = R.drawable.ic_hamburger),
contentDescription = "hamburger button",
)
Spacer(modifier = Modifier.size(16.dp))
Box(
modifier = Modifier.height(80.dp),
contentAlignment = Alignment.Center
) {
Text(
text = "Item ${item.id}",
textAlign = TextAlign.Center,
)
}
}
HorizontalDivider(
modifier = Modifier
.fillMaxWidth()
.height(1.dp),
color = Color.Black,
)
}
}
}
Но это так странно, потому что я могу перетащить только первый элемент, хотя я перетащил 2 или 3 элемента в LazyColumn.

Что здесь происходит? Как я могу создать DragAndDrop, перетащив определенную область внутри элемента LazyColumn?
[Изменить]
Этот код все еще странный но я изменил кое-какой код. Я могу справиться с кнопкой-гамбургером, но когда я перенесу куда-нибудь первый элемент, он сломается. пожалуйста, проверьте код, и я надеюсь, что мы сможем решить эту проблему вместе.
Полный код
MainActivity.kt
@ExperimentalFoundationApi
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
DraggableLazyListTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
val items = remember {
mutableStateListOf(ReorderItem(id = 1, title = "1 item"),
ReorderItem(id = 2, title = "2 item"),
ReorderItem(id = 3, title = "3 item"),
ReorderItem(id = 4, title = "4 item"),
ReorderItem(id = 5, title = "5 item"),
ReorderItem(id = 6, title = "6 item"),
ReorderItem(id = 7, title = "7 item"),
ReorderItem(id = 8, title = "8 item"),
ReorderItem(id = 9, title = "9 item"),
ReorderItem(id = 10, title = "10 item"),
ReorderItem(id = 11, title = "11 item"),
ReorderItem(id = 12, title = "12 item"),
ReorderItem(id = 13, title = "13 item"),
ReorderItem(id = 14, title = "14 item"),
ReorderItem(id = 15, title = "15 item"),
ReorderItem(id = 16, title = "16 item"),
ReorderItem(id = 17, title = "17 item"),
ReorderItem(id = 18, title = "18 item"),)
}
DragDropList(items = items.toList(), onMove = { fromPosition, toPosition ->
items.move(fromPosition, toPosition)
})
}
}
}
}
}
DragDropList.kt [отредактировано]
@ExperimentalFoundationApi
@Composable
fun DragDropList(
items: List,
onMove: (Int, Int) -> Unit,
modifier: Modifier = Modifier
) {
val scope = rememberCoroutineScope()
var overscrollJob by remember { mutableStateOf(null) }
val dragDropListState = rememberDragDropListState(onMove = onMove)
LazyColumn(
modifier = modifier,
state = dragDropListState.lazyListState,
) {
itemsIndexed(
items = items,
key = { _, item -> item.id }
) { index, item ->
val isCurrentItem = index == dragDropListState.currentIndexOfDraggedItem
val draggingModifier = if (isCurrentItem) {
Modifier
.zIndex(1f)
.graphicsLayer {
translationY = dragDropListState.elementDisplacement ?: 0f
}
} else {
Modifier
.zIndex(0f)
.animateItemPlacement(
animationSpec = tween(
durationMillis = 300,
)
)
}
Row(
modifier = draggingModifier
.background(Color.White, shape = RoundedCornerShape(4.dp))
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
) {
Image(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_hamburger),
contentDescription = "hamburger button",
modifier = Modifier
.pointerInput(item.id) {
detectDragGesturesImmediately(
onDragStart = { offset ->
dragDropListState.onDragStart(offset, item.id)
},
onDrag = { change, offset ->
change.consume()
dragDropListState.onDrag(offset)
if (overscrollJob?.isActive == true)
return@detectDragGesturesImmediately
dragDropListState.checkForOverScroll()
.takeIf { it != 0f }
?.let {
Log.d("TEST", "scroll float = $it")
overscrollJob = scope.launch {
dragDropListState.lazyListState.scrollBy(it)
}
}
?: run { overscrollJob?.cancel() }
},
onDragEnd = { dragDropListState.onDragInterrupted() },
onDragCancel = { dragDropListState.onDragInterrupted() }
)
}
)
Spacer(modifier = Modifier.width(16.dp))
Box(
modifier = Modifier.height(80.dp),
contentAlignment = Alignment.Center
) {
Text(
text = "Item ${item.id}",
textAlign = TextAlign.Center,
)
}
}
HorizontalDivider(
modifier = Modifier
.fillMaxWidth()
.height(1.dp),
color = Color.Black,
)
}
}
}
DragGestureDectectorEx.kt
suspend fun PointerInputScope.detectDragGesturesImmediately(
onDragStart: (Offset) -> Unit = { },
onDragEnd: () -> Unit = { },
onDragCancel: () -> Unit = { },
onDrag: (change: PointerInputChange, dragAmount: Offset) -> Unit
) {
awaitEachGesture {
try {
val down = awaitFirstDown(requireUnconsumed = false)
onDragStart.invoke(down.position)
if (
drag(down.id) {
onDrag(it, it.positionChange())
it.consume()
}
) {
// consume up if we quit drag gracefully with the up
currentEvent.changes.fastForEach {
if (it.changedToUp()) it.consume()
}
onDragEnd()
} else {
onDragCancel()
}
} catch (c: CancellationException) {
onDragCancel()
throw c
}
}
}
**DragDropListState.kt**
@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(null)
var currentIndexOfDraggedItem by mutableStateOf(null)
val initialOffsets: Pair?
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(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
}
}
RedorderableList_Extensions.kt
/*
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 MutableList.move(from: Int, to: Int) {
Collections.swap(this, from, to)
}
Подробнее здесь: https://stackoverflow.com/questions/787 ... ck-compose
Мобильная версия