【Android】 Jetpack Composeでドラッグ＆ドロップの並び替えを実現する 〜ライブラリに頼って〜

この記事は、[and factory.inc Advent Calendar 2021](https://qiita.com/advent-calendar/2021/andfactory)の **5日目** の記事です。
昨日は [@y-okudera](https://qiita.com/y-okudera) さんの「 [データの暗号化・復号とSwiftでの実装の仕方について改めて調べてみた](https://qiita.com/y-okudera/items/d52d3c1b3fb82176c78a) 」でした！

# ドラッグ＆ドロップで並び替えしたい
リスト表示において、アイテムを長押しするとドラッグ＆ドロップで並び替えができる、というのは結構あるあるな機能ではないでしょうか？

RecyclerViewでは[ItemTouchHelper](https://developer.android.com/reference/androidx/recyclerview/widget/ItemTouchHelper)を使うことで、ドラッグ＆ドロップでの並び替えを実装することができました。

しかしながら、Jetpack Composeにおいては、現状ドラッグ＆ドロップはサポートされておりません。
一応[ロードマップ](https://developer.android.google.cn/jetpack/androidx/compose-roadmap)には「**ドラッグ＆ドロップのサポート**」が記載されているので、今後公式にサポートされるものと思われます。

「公式にサポートされるまで待てないよ…！」という欲求に駆られたため、今回は**ライブラリの力に頼って** :upside_down: ドラッグ＆ドロップでの並び替えを実現してみたいと思います。

# :pray: aclassen/ComposeReorderable :pray: 

https://github.com/aclassen/ComposeReorderable

`LazyList` （ `LazyColumn`、`LazyRow` ）のドラッグ＆ドロップでの並び替えを可能にするライブラリです。

こちらのライブラリの使い方を説明していきたいと思います。

# Before
シンプルな `LazyColumn` を使った画面に、 `ComposeReorderable` を用いたドラッグ＆ドロップでの並び替えを追加していくことを想定します。

対応前のコード↓

```kotlin
    Scaffold(
        topBar = {
            TopAppBar(title = { Text("Sample") })
        }
    ) {
        val data = remember {
            mutableStateOf(List(100) { "$it" })
        }

        LazyColumn {
            items(data.value) { item ->
                Box(
                    modifier = Modifier
                        .fillMaxWidth()
                        .clickable {}
                        .padding(40.dp),
                ) {
                    Text(item)
                }
                Divider(thickness = 1.dp)
            }
        }
    }
```

画面としては↓のような感じです。
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/995/5ba5fee8-f358-8c13-618d-e69418d50cef.png width=320 />



# 1. dependenciesへの追加

`dependencies` に追加します。記事作成時では `0.7.4` が最新版でした。

```
+    implementation "org.burnoutcrew.composereorderable:reorderable:0.7.4"
```

# 2. LazyColumnへ設定追加
① `rememberReorderState()` を呼び出し `ReorderableState` を作成します。

② 作成した `ReorderableState` の `listState` を、 `LazyColumn` の `state` に指定します。

③ `ComposeReorderable` の拡張である `reorderable` Modifierも設定します。

```kotlin
-        LazyColumn {
+        val reorderState = rememberReorderState() // ①
+
+        LazyColumn(
+            state = reorderState.listState, // ②
+            modifier = Modifier // ③
+                .reorderable(
+                    state = reorderState,
+                    onMove = { from, to -> /* TODO */ },
+                ),
+        ) {
```

# 3. リストのアイテムへの設定追加
リストの各アイテムの `Modifier` へ設定を追加します。

① 並び替え可能なアイテムであることを示すために、`detectReorder(state)` or `detectReorderAfterLongPress(state)` を指定します。
`detectReorderAfterLongPress(state)` の場合は、長押しで並び替えが起きるようになるので、今回はこちらを指定します。

② `draggedItem` の指定も追加します。
引数の `offset` には `reorderState.offsetByKey` or `reorderState.offsetByIndex` を使って指定します。



```kotlin
         ) {
-            items(data.value) { item ->
+            itemsIndexed(data.value) { index, item ->
                 Box(
                     modifier = Modifier
                         .fillMaxWidth()
                         .clickable { /**/ }
+                        .detectReorderAfterLongPress(reorderState) // ①
+                        .draggedItem(reorderState.offsetByIndex(index)) // ②
                         .padding(40.dp),
                 ) {
                     Text(item)
```

一応この時点で、ドラッグ＆ドロップは動くようになっています…！
ただ、データ自体が変わっていないため、ドラッグしているアイテムがおかしなことになっているので、2で `/* TODO */` にしていた `onMove` の処理を実装します。

![1638551030.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/995/d3db184c-e68e-3257-d873-b97030abc04b.gif)

# 4. データの更新実装
ドラッグ＆ドロップでアイテムを動かし、他のアイテムと切り替わるタイミングで `onMove` が呼ばれるので、そのタイミングでデータの更新をしてあげる必要があります。

※ `move` は `ComposeReorderable` に入っている拡張関数です。

```kotlin
-          onMove = { from, to -> },
+          onMove = { from, to ->
+              data.value = data.value.toMutableList().apply { move(from.index, to.index) }
+          },
```

以上の工程で、ドラッグ＆ドロップによる並び替えを実装することができました🎉
![1638551845.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/995/33db576e-11fa-05c3-6a61-038829cf8f7a.gif)

# After

Beforeの対比としてAfterのコードも載せておきます。


```kotlin
    Scaffold(
        topBar = {
            TopAppBar(title = { Text("Sample") })
        }
    ) {
        val data = remember {
            mutableStateOf(List(100) { "$it" })
        }

        val reorderState = rememberReorderState()

        LazyColumn(
            state = reorderState.listState,
            modifier = Modifier
                .reorderable(
                    state = reorderState,
                    onMove = { from, to ->
                        data.value = data.value.toMutableList().apply { move(from.index, to.index) }
                    },
                ),
        ) {
            itemsIndexed(data.value) { index, item ->
                Box(
                    modifier = Modifier
                        .fillMaxWidth()
                        .clickable {}
                        .detectReorder(reorderState)
                        .draggedItem(reorderState.offsetByIndex(index))
                        .padding(40.dp),
                ) {
                    Text(item)
                }
                Divider(thickness = 1.dp)
            }
        }
    }
```

# 終わりに
（ただのライブラリの使い方の説明記事になってしまいましたが…）
Jetpack Composeでドラッグ＆ドロップの並び替えを実現することができました。
「公式にサポートされるまで待てないよ…！」という方の参考になれば幸いです。