Skip to content

Commit

Permalink
Merge pull request #100 from aclassen/feature/refactor
Browse files Browse the repository at this point in the history
Refactored to provide better animation support
  • Loading branch information
aclassen authored Jun 1, 2022
2 parents 5b418c9 + 8229559 commit 83e650e
Show file tree
Hide file tree
Showing 23 changed files with 700 additions and 637 deletions.
83 changes: 17 additions & 66 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,86 +15,37 @@ dependencies {

## How to use

Create `reorderState` and add the `reorderable` Modifier to the LazyList/Grid:

```
// For a LazyGrid just use `rememberReorderLazyListState`
val state: ReorderableLazyListState = rememberReorderLazyListState(onMove = { from, to -> data.move(from.index, to.index) })
LazyColumn(
state = state.listState,
modifier = Modifier.reorderable(state)) {
...
}
```

For a LazyGrid just use `rememberReorderLazyListState`
To make an item reorderable/draggable add at least one drag modifier to the item:

```
Modifier.detectReorder(state)
or
Modifier.detectReorderAfterLongPress(state)
```

> Adding one of the detect modifiers to the LazyList instead of an item , will make all items reordable.
At least apply the dragged item offset:

```
items(items, { it.key }) {item ->
Column(
modifier = Modifier.draggedItem(state.offsetByKey(item.key))
) {
...
}
}
or without keyed items:
itemsIndexed(items) { idx, item ->
Column(
modifier = Modifier.draggedItem(state.offsetByIndex(idx))
) {
...
}
}
```

> You can use `draggedItem` for a default dragged effect or create your own.
Complete example:
```
@Composable
fun ReorderableList(){
val data = List(100) { "item $it" }.toMutableStateList()
val state: ReorderableLazyListState = rememberReorderLazyListState(onMove = { from, to -> data.move(from.index, to.index) })
fun VerticalReorderList() {
val data = remember { mutableStateOf(List(100) { "Item $it" }) }
val state = rememberReorderableLazyListState(onMove = { from, to ->
data.value = data.value.toMutableList().apply {
add(to.index, removeAt(from.index))
}
})
LazyColumn(
state = state.listState,
modifier = Modifier.reorderable(state)
) {
items(data, { it }) { item ->
Box(
modifier = Modifier
.fillMaxWidth()
.draggedItem(state.offsetByKey(item))
.detectReorderAfterLongPress(state)
) {
Text(text = item)
items(data.value, { it }) { item ->
ReorderableItem(state, key = item) { isDragging ->
Column(
modifier = Modifier
.background(MaterialTheme.colors.surface)
.detectReorderAfterLongPress(state)
) {
Text(item)
}
}
}
}
}
```


## Notes

When dragging, the existing item will be modified. Because of that it's important that the item must be part of the LazyList visible
items all the time.

This can be problematic if no drop target can be found during scrolling.
It's a known issue that the first visible item does not animate.

## License

Expand Down
10 changes: 5 additions & 5 deletions android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ plugins {
}

dependencies {
implementation("org.burnoutcrew.composereorderable:reorderable:0.8.1")
implementation("androidx.compose.runtime:runtime:1.2.0-beta01")
implementation("androidx.compose.material:material:1.2.0-beta01")
implementation("org.burnoutcrew.composereorderable:reorderable:0.9.0")
implementation("androidx.compose.runtime:runtime:1.2.0-beta02")
implementation("androidx.compose.material:material:1.2.0-beta02")
implementation("androidx.activity:activity-compose:1.4.0")
implementation("com.google.android.material:material:1.6.0")
implementation("com.google.android.material:material:1.6.1")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.4.1")
implementation("androidx.navigation:navigation-compose:2.5.0-rc01")
implementation("io.coil-kt:coil-compose:1.4.0")
implementation("io.coil-kt:coil-compose:2.1.0")
}

android {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 André Claßen
* Copyright 2022 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.
Expand All @@ -15,21 +15,22 @@
*/
package org.burnoutcrew.android.ui.reorderlist

import androidx.compose.runtime.toMutableStateList
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import org.burnoutcrew.reorderable.ItemPosition
import org.burnoutcrew.reorderable.move

import kotlin.random.Random


class ImageListViewModel : ViewModel() {
val images = List(20) { "https://picsum.photos/seed/compose$it/200/300" }.toMutableStateList()
val headerImage = "https://picsum.photos/seed/compose${Random.nextInt(Int.MAX_VALUE)}/400/200"
val footerImage = "https://picsum.photos/seed/compose${Random.nextInt(Int.MAX_VALUE)}/400/200"

var images by mutableStateOf(List(20) { "https://picsum.photos/seed/compose$it/200/300" })
fun onMove(from: ItemPosition, to: ItemPosition) {
images.move(images.indexOfFirst { it == from.key }, images.indexOfFirst { it == to.key })
images = images.toMutableList().apply {
add(images.indexOfFirst { it == to.key }, removeAt(images.indexOfFirst { it == from.key }))
}
}

fun canDragOver(pos: ItemPosition) = images.any { it == pos.key }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 André Claßen
* Copyright 2022 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.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 André Claßen
* Copyright 2022 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.
Expand All @@ -15,10 +15,12 @@
*/
package org.burnoutcrew.android.ui.reorderlist


import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
Expand All @@ -32,85 +34,96 @@ import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import org.burnoutcrew.reorderable.ItemPosition
import org.burnoutcrew.reorderable.ReorderableItem
import org.burnoutcrew.reorderable.detectReorderAfterLongPress
import org.burnoutcrew.reorderable.draggedItem
import org.burnoutcrew.reorderable.rememberReorderableLazyGridState
import org.burnoutcrew.reorderable.reorderable


@Composable
fun ReorderGrid(vm: ReorderListViewModel = viewModel()) {
Column {
HorizontalGrid(
items = vm.cats,
modifier = Modifier.padding(vertical = 16.dp),
onMove = { from, to -> vm.moveCat(from, to) },
)
VerticalGrid(
items = vm.dogs,
onMove = { from, to -> vm.moveDog(from, to) },
canDragOver = { vm.isDogDragEnabled(it) },
vm = vm,
modifier = Modifier.padding(vertical = 16.dp)
)
VerticalGrid(vm = vm)
}
}


@Composable
private fun HorizontalGrid(
vm: ReorderListViewModel,
modifier: Modifier = Modifier,
items: List<ItemData>,
onMove: (fromPos: ItemPosition, toPos: ItemPosition) -> (Unit),
) {
val state = rememberReorderableLazyGridState(onMove = onMove)
val state = rememberReorderableLazyGridState(onMove = vm::moveCat)
LazyHorizontalGrid(
rows = GridCells.Fixed(2),
state = state.gridState,
contentPadding = PaddingValues(horizontal = 8.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp),
modifier = modifier.reorderable(state).height(200.dp)
) {
items(items, { it.key }) { item ->
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.aspectRatio(1f)
.padding(4.dp)
.draggedItem(offset = state.offsetByKey(item.key))
.background(MaterialTheme.colors.secondary)
.detectReorderAfterLongPress(state)
) {
Text(item.title)
items(vm.cats, { it.key }) { item ->
ReorderableItem(state, item.key) { isDragging ->
val elevation = animateDpAsState(if (isDragging) 16.dp else 0.dp)
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.shadow(elevation.value)
.aspectRatio(1f)
.background(MaterialTheme.colors.secondary)
.detectReorderAfterLongPress(state)
) {
Text(item.title)
}
}
}
}
}

@Composable
private fun VerticalGrid(
vm: ReorderListViewModel,
modifier: Modifier = Modifier,
items: List<ItemData>,
onMove: (fromPos: ItemPosition, toPos: ItemPosition) -> (Unit),
canDragOver: ((pos: ItemPosition) -> Boolean),
) {
val state = rememberReorderableLazyGridState(onMove = onMove, canDragOver = canDragOver)
val state = rememberReorderableLazyGridState(onMove = vm::moveDog, canDragOver = vm::isDogDragEnabled)
LazyVerticalGrid(
columns = GridCells.Fixed(4),
state = state.gridState,
contentPadding = PaddingValues(horizontal = 8.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp),
modifier = modifier.reorderable(state)
) {
items(items, { it.key }) { item ->
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.size(100.dp)
.padding(4.dp)
.draggedItem(state.offsetByKey(item.key))
.background(MaterialTheme.colors.primary)
.detectReorderAfterLongPress(state)
) {
Text(item.title)
items(vm.dogs, { it.key }) { item ->
ReorderableItem(state, item.key) { isDragging ->
val elevation = animateDpAsState(if (isDragging) 8.dp else 0.dp)
if (item.isLocked) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.size(100.dp)
.background(MaterialTheme.colors.surface)
) {
Text(item.title)
}
} else {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.shadow(elevation.value)
.aspectRatio(1f)
.background(MaterialTheme.colors.primary)
.detectReorderAfterLongPress(state)
) {
Text(item.title)
}
}
}
}
}
}
}
Loading

0 comments on commit 83e650e

Please sign in to comment.