Connect with us

Code

Reorderable – Reorder items in LazyColumn and LazyRow

Reorderable is a simple library that allows you to reorder items in LazyColumn and LazyRow as well as Column and Row in Jetpack Compose with drag and drop.

Features

  • Supports items of different sizes
  • Some items can be made non-reorderable
  • Scrolls when dragging to the edge of the screen (only for LazyColumn and LazyRow) The scroll speed is based on the distance from the edge of the screen
  • Uses the new Modifier.animateItemPlacement API to animate item movement in LazyColumn and LazyRow
  • Supports using a child of an item as the drag handle

Usage

Gradle

Add the following to your build.gradle file:

Kotlin DSL

dependencies {
    implementation("sh.calvin.reorderable:reorderable:1.0.1")
}

Groovy DSL

dependencies {
    implementation 'sh.calvin.reorderable:reorderable:1.0.1'
}

Code

See demo app code for more examples.

LazyColumn

Find more examples in SimpleReorderableLazyColumnScreen.kt and ComplexReorderableLazyColumnScreen.kt in the demo app.

val lazyListState = rememberLazyListState()
val reorderableLazyColumnState = rememberReorderableLazyColumnState(lazyListState) { from, to ->
    // Update the list
}

LazyColumn(state = lazyListState) {
    items(list, key = { /* item key */ }) {
        ReorderableItem(reorderableLazyColumnState, key = /* item key */) { isDragging ->
            // Item content

            IconButton(modifier = Modifier.draggableHandle(), /* ... */)
        }
    }
}

Since Modifier.draggableHandle can only be used in ReorderableItemScope, you may need to pass ReorderableItemScope to a child composable. For example:

@Composable
fun List() {
    // ...

    LazyColumn(state = lazyListState) {
        items(list, key = { /* item key */ }) {
            ReorderableItem(reorderableLazyColumnState, key = /* item key */) { isDragging ->
                // Item content

                DragHandle(this)
            }
        }
    }
}

@Composable
fun DragHandle(scope: ReorderableItemScope) {
    IconButton(modifier = with(scope) { Modifier.draggableHandle() }, /* ... */)
}

Here’s a more complete example with (with haptic feedback):

val view = LocalView.current

var list by remember { mutableStateOf(List(100) { "Item $it" }) }
val lazyListState = rememberLazyListState()
val reorderableLazyColumnState = rememberReorderableLazyColumnState(lazyListState) { from, to ->
    list = list.toMutableList().apply {
        add(to.index, removeAt(from.index))
    }

    view.performHapticFeedback(HapticFeedbackConstants.SEGMENT_FREQUENT_TICK)
}

LazyColumn(
    modifier = Modifier.fillMaxSize(),
    state = lazyListState,
    contentPadding = PaddingValues(8.dp),
    verticalArrangement = Arrangement.spacedBy(8.dp)
) {
    items(list, key = { it }) {
        ReorderableItem(reorderableLazyColumnState, key = it) { isDragging ->
            val elevation by animateDpAsState(if (isDragging) 4.dp else 0.dp)

            Surface(shadowElevation = elevation) {
                Row {
                    Text(it, Modifier.padding(horizontal = 8.dp))
                    IconButton(
                        modifier = Modifier.draggableHandle(
                            onDragStarted = {
                                view.performHapticFeedback(HapticFeedbackConstants.DRAG_START)
                            },
                            onDragStopped = {
                                view.performHapticFeedback(HapticFeedbackConstants.GESTURE_END)
                            },
                        ),
                        onClick = {},
                    ) {
                        Icon(Icons.Rounded.DragHandle, contentDescription = "Reorder")
                    }
                }
            }
        }
    }
}

Column

Find more examples in ReorderableColumnScreen.kt in the demo app.

ReorderableColumn(
    list = list,
    onSettle = { fromIndex, toIndex ->
        // Update the list
    },
) { index, item, isDragging ->
    key(item.id) {
        // Item content

        IconButton(modifier = Modifier.draggableHandle(), /* ... */)
    }
}

Since Modifier.draggableHandle can only be used in ReorderableScope, you may need to pass ReorderableScope to a child composable. For example:

@Composable
fun List() {
    // ...

    ReorderableColumn(
        list = list,
        onSettle = { fromIndex, toIndex ->
            // Update the list
        },
    ) { index, item, isDragging ->
        key(item.id) {
            // Item content

            DragHandle(this)
        }
    }
}

@Composable
fun DragHandle(scope: ReorderableScope) {
    IconButton(modifier = with(scope) { Modifier.draggableHandle() }, /* ... */)
}

Here’s a more complete example (with haptic feedback):

val view = LocalView.current

var list by remember { mutableStateOf(List(4) { "Item $it" }) }

ReorderableColumn(
    modifier = Modifier
        .fillMaxSize()
        .padding(8.dp),
    list = list,
    onSettle = { fromIndex, toIndex ->
        list = list.toMutableList().apply {
            add(toIndex, removeAt(fromIndex))
        }
    },
    onMove = {
        view.performHapticFeedback(HapticFeedbackConstants.SEGMENT_FREQUENT_TICK)
    },
    verticalArrangement = Arrangement.spacedBy(8.dp),
) { _, item, isDragging ->
    key(item) {
        val elevation by animateDpAsState(if (isDragging) 4.dp else 0.dp)

        Surface(shadowElevation = elevation) {
            Row {
                Text(item, Modifier.padding(horizontal = 8.dp))
                IconButton(
                    modifier = Modifier.draggableHandle(
                        onDragStarted = {
                            view.performHapticFeedback(HapticFeedbackConstants.DRAG_START)
                        },
                        onDragStopped = {
                            view.performHapticFeedback(HapticFeedbackConstants.GESTURE_END)
                        },
                    ),
                    onClick = {},
                ) {
                    Icon(Icons.Rounded.DragHandle, contentDescription = "Reorder")
                }
            }
        }
    }
}

LazyRow

See SimpleReorderableLazyRowScreen.kt and ComplexReorderableLazyRowScreen.kt in the demo app.

You can just replace Column with Row in the LazyColumn examples above.

Row

See ReorderableRowScreen.kt in the demo app.

You can just replace Column with Row in the Column examples above.

API

LazyColumn / LazyRow

Column / Row

 

Reorderable on GItHub: https://github.com/Calvin-LL/Reorderable/
Platform: Android
⭐️: 30
Advertisement

Trending