Skip to content

Commit

Permalink
Drag handle support
Browse files Browse the repository at this point in the history
  • Loading branch information
aclassen committed Aug 8, 2021
1 parent 6f62bdb commit df6a255
Show file tree
Hide file tree
Showing 8 changed files with 390 additions and 301 deletions.
47 changes: 28 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,45 +22,54 @@ dependencies {

## How to use

Create your LazyColumn or LazyRow:
Add a `Reorderable` to your composition:

```
val state: ReorderableState = rememberReorderState { from, to -> data.move(from, to) }
LazyColumn(
state = state.listState,
modifier = Modifier.reorderable(state, items))
val state: ReorderableState = rememberReorderState()
Reorderable(state, { from, to -> data.move(from, to) })
LazyColumn(state = state.listState) {
...
}
```

Apply the offset to your item layout :
To apply the dragged item offset:

```
items(items, { it.key }) {item ->
Column(
modifier = Modifier.draggedItem(state.draggedOffset.takeIf { state.draggedKey == item.key })
modifier = Modifier.draggedItem(state.offsetOf(item.key))
) {
...
}
}
```

without keyed items:
Make an item reorderable by adding at least one drag modifier to the item:

```
itemsIndexed(items) { idx, item ->
Column(
modifier = Modifier.draggedItem(state.draggedOffset.takeIf { state.draggedIndex == idx })
) {
...
}
}
Modifier.detectReorder(state, { item.key })
or
Modifier.detectReorderAfterLongPress(state, { item.key })
```
Use `draggedItem` for a default dragged effect or create your own.

## Notes

It`s recommended to use keyed items, especially if item size is not equal.
If you want to use a non keyed item list `detectReorder` and `detectReorderAfterLongPress` will not work , use the `detectListReorder` modifier instead.

Add this modifier to your LazyList , this will make the items reorderable after long press.

```
Reorderable(state, { from, to -> data.move(from, to) })
LazyRow(
state = state.listState,
modifier = Modifier
.detectListReorder(state),
)
```

Use `draggedItem` for a default dragged effect or create your own.

## Notes
When dragging, the existing item will be modified.
Because if this reason it`s important that the item must be part of the LazyList visible items all the time.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,26 +38,20 @@ import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import io.burnoutcrew.reorderable.ReorderableState
import io.burnoutcrew.reorderable.draggedItem
import io.burnoutcrew.reorderable.rememberReorderState
import io.burnoutcrew.reorderable.reorderable
import io.burnoutcrew.reorderable.*

@Composable
fun ReorderList(vm: ReorderListViewModel = viewModel()) {
Column {
HorizontalReorderList(
items = vm.cats,
state = rememberReorderState(onMove = { from, to -> vm.moveCat(from, to) }),
modifier = Modifier.padding(vertical = 16.dp),
onMove = { from, to -> vm.moveCat(from, to) },
)
VerticalReorderList(
items = vm.dogs,
state = rememberReorderState(
onMove = { from, to -> vm.moveDog(from, to) },
canDragOver = { vm.isDogDragEnabled(it) },
isDragEnabled = { vm.isDogDragEnabled(it) },
)
onMove = { from, to -> vm.moveDog(from, to) },
canDragOver = { vm.isDogDragEnabled(it) },
)
}
}
Expand All @@ -66,13 +60,15 @@ fun ReorderList(vm: ReorderListViewModel = viewModel()) {
fun HorizontalReorderList(
modifier: Modifier = Modifier,
items: List<ItemData>,
state: ReorderableState,
state: ReorderableState = rememberReorderState(),
onMove: (fromPos: Int, toPos: Int) -> (Unit),
) {
Reorderable(state, onMove)
LazyRow(
state = state.listState,
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier
.reorderable(state, Orientation.Horizontal)
.detectListReorder(state, Orientation.Horizontal)
.then(modifier),
) {
itemsIndexed(items) { idx, item ->
Expand All @@ -81,7 +77,7 @@ fun HorizontalReorderList(
modifier = Modifier
.size(100.dp)
.draggedItem(
offset = if (state.draggedIndex == idx) state.draggedOffset else null,
offset = state.offsetOf(idx),
orientation = Orientation.Horizontal
)
.scale(if (state.draggedIndex == null || state.draggedIndex == idx) 1f else .9f)
Expand All @@ -98,13 +94,14 @@ fun HorizontalReorderList(
fun VerticalReorderList(
modifier: Modifier = Modifier,
items: List<ItemData>,
state: ReorderableState,
state: ReorderableState = rememberReorderState(),
onMove: (fromPos: Int, toPos: Int) -> (Unit),
canDragOver: ((index: Int) -> Boolean),
) {
Reorderable(state, onMove, canDragOver)
LazyColumn(
state = state.listState,
modifier = Modifier
.reorderable(state)
.then(modifier)
modifier = modifier
) {
items(items, { it.key }) { item ->
if (item.isLocked) {
Expand All @@ -122,7 +119,7 @@ fun VerticalReorderList(
Column(
modifier = Modifier
.fillMaxWidth()
.draggedItem(if (state.draggedKey == item.key) state.draggedOffset else null)
.draggedItem(state.offsetOf(item.key))
.background(MaterialTheme.colors.surface)
) {
Row(
Expand All @@ -131,7 +128,8 @@ fun VerticalReorderList(
) {
Image(
imageVector = Icons.Filled.Menu,
contentDescription = ""
contentDescription = "",
modifier = Modifier.detectReorder(state, { item.key })
)
Text(
text = item.title,
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ buildscript {
}
dependencies {
classpath("com.android.tools.build:gradle:7.1.0-alpha03")
classpath(kotlin("gradle-plugin", "1.5.10"))
classpath(kotlin("gradle-plugin", "1.5.21"))
}
}

Expand Down
4 changes: 1 addition & 3 deletions reorderable/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@ plugins {
id("maven-publish")
}


android {
compileSdk = rootProject.extra.get("compileSdk") as Int


defaultConfig {
minSdk = rootProject.extra.get("minVersion") as Int
targetSdk = rootProject.extra.get("targetSdk") as Int
Expand Down Expand Up @@ -64,7 +62,7 @@ afterEvaluate {
publications {
create<MavenPublication>("debug") {
groupId = "com.github.aclassen"
version = "0.1"
version = "0.3"
from(components["debug"])
artifact(sourcesJar)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Copyright 2021 André Claßen
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.burnoutcrew.reorderable

import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.consumeAllChanges
import androidx.compose.ui.input.pointer.pointerInput

fun Modifier.detectReorder(
state: ReorderableState,
key: () -> (Any),
orientation: Orientation = Orientation.Vertical,
) =
this.then(
Modifier.pointerInput(Unit) {
detectDragGestures(
onDragStart = {
state.interactionSource.tryEmit(ReorderInteraction.Start(key()))
},
onDragEnd = {
state.interactionSource.tryEmit(ReorderInteraction.End)
},
onDragCancel = {
state.interactionSource.tryEmit(ReorderInteraction.End)
},
onDrag = { change, dragAmount ->
change.consumeAllChanges()
state.interactionSource.tryEmit(ReorderInteraction.Drag(dragAmount.forOrientation(orientation)))
})
}
)

fun Modifier.detectReorderAfterLongPress(
state: ReorderableState,
key: () -> (Any),
orientation: Orientation = Orientation.Vertical,
) =
this.then(
Modifier.pointerInput(Unit) {
detectDragGesturesAfterLongPress(
onDragStart = {
state.interactionSource.tryEmit(ReorderInteraction.Start(key()))
},
onDragEnd = {
state.interactionSource.tryEmit(ReorderInteraction.End)
},
onDragCancel = {
state.interactionSource.tryEmit(ReorderInteraction.End)
},
onDrag = { change, dragAmount ->
change.consumeAllChanges()
state.interactionSource.tryEmit(ReorderInteraction.Drag(dragAmount.forOrientation(orientation)))
})
}
)

fun Modifier.detectListReorder(
state: ReorderableState,
orientation: Orientation = Orientation.Vertical,
isDragEnabled: ((index: Int) -> Boolean)? = null,
) =
this.then(
Modifier.pointerInput(Unit) {
detectDragGesturesAfterLongPress(
onDragStart = { offset ->
val off = offset.forOrientation(orientation).toInt()
state.listState.layoutInfo.visibleItemsInfo
.firstOrNull { off in it.offset..(it.offset + it.size) }
?.takeIf { isDragEnabled?.invoke(it.index) != false }
?.also {
state.interactionSource.tryEmit(ReorderInteraction.Start(it.key))
}
},
onDragEnd = {
state.interactionSource.tryEmit(ReorderInteraction.End)
},
onDragCancel = {
state.interactionSource.tryEmit(ReorderInteraction.End)
},
onDrag = { change, dragAmount ->
change.consumeAllChanges()
state.interactionSource.tryEmit(ReorderInteraction.Drag(dragAmount.forOrientation(orientation)))
})
})

private fun Offset.forOrientation(orientation: Orientation) = if (orientation == Orientation.Vertical) y else x
Loading

0 comments on commit df6a255

Please sign in to comment.