ComposeのDragAndDrop系のModifier

[compose foundation 1.6](https://developer.android.com/jetpack/androidx/releases/compose-foundation#version_16_2)  で Experimentalだったドラッグ&ドロップ周りのModifierが来てたのでいくつか記事にします（させます）

## `dragAndDropSource`

この機能を利用すると、任意のComposableにドラッグ動作を提供できます。以下に一般的なAPIの形式を示します。

```kotlin
@ExperimentalFoundationApi
fun Modifier.dragAndDropSource(
    drawDragDecoration: DrawScope.() -> Unit,
    block: suspend DragAndDropSourceScope.() -> Unit
): Modifier
```
### 例: 文字をドラッグ可能にする

![画面収録 2024-01-07 8.44.00.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/700755/18ce2974-23e0-b468-daca-e1229d7d7ee5.gif)

下記のコンポーザブル関数例では、ドラッグして文字列の転送を開始するボックスが定義されています。

```kotlin
@Composable
fun DragAndDropSourceSample() {
    val color = Color.Red
    Box(
        modifier = Modifier
            .size(56.dp)
            .background(color = color)
            .dragAndDropSource(
                drawDragDecoration = { drawRect(color) },
            ) { // DragAndDropSourceScope
                detectTapGestures(
                    onLongPress = {
                        startTransfer( // 送るデータ
                            transferData = DragAndDropTransferData(
                                ClipData.newPlainText("text", "text"),
                            ),
                        )
                    }
                )
            }
    )
}
```

`drawDragDecoration`はドラッグ中の視覚効果をDrawScopeで描画します。また、`DragAndDropSourceScope`は`PointerInputScope`が提供され、これを用いてどのジェスチャで`startTransfer`（データ転送）をするかを定義します。

## `dragAndDropTarget`を用いたドロップ操作の追加

ドラッグ対象に対して、以下のような形式で`dragAndDropTarget`機能を追加することができます。

```kotlin
@ExperimentalFoundationApi
fun Modifier.dragAndDropTarget(
    shouldStartDragAndDrop: (startEvent: DragAndDropEvent) -> Boolean,
    target: DragAndDropTarget
): Modifier
```

![dragTarget.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/700755/aa2d7dc8-d6b3-346b-1e43-16667fb25ad2.gif)


## アンカーを設定する`DraggableAnchors`関数

```kotlin
@ExperimentalFoundationApi
fun <T : Any> DraggableAnchors(
    builder: DraggableAnchorsConfig<T>.() -> Unit
): DraggableAnchors<T> = MapDraggableAnchors(DraggableAnchorsConfig<T>().apply(builder).anchors)
```

特定の位置にアンカーを設定し、そのアンカー間でのドラッグを可能にする`DraggableAnchors`関数についても触れておくと良いでしょう。それにより、ユーザーは特定の範囲内でコントロールの移動を体験することができます。

```kotlin
DraggableAnchors {
    T at 0f
    T at 300f
}
```

上記のようにアンカーを設定し、`anchoredDraggable`を使って制御することができます。

例えば、以下のコードはスタートとエンドの位置にアンカーを設定し、アイコンをそれらのアンカー間でドラッグできるようにしています。

![Anchored (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/700755/a1cacfc4-44f0-492e-0f10-be64e5cba30a.gif)


```kotlin
enum class AnchorPos {
    Start, End;
}
...
val density = LocalDensity.current
val draggableState = remember {
    AnchoredDraggableState(
        initialValue = AnchorPos.Start,
        anchors = DraggableAnchors {
            AnchorPos.Start at 0f
            AnchorPos.End at 300f
        },
        positionalThreshold = { totalDistance -> totalDistance * 0.5f},
        velocityThreshold = { with(density) { 100.dp.toPx()} },
        animationSpec = tween(),
        confirmValueChange = { true }
    )
}
Box(modifier = Modifier.fillMaxSize()) {
    Image(
        imageVector = Icons.Default.FavoriteBorder,
        contentDescription = "",
        modifier = Modifier
            .offset(
                x = with(density) {
                    draggableState
                        .requireOffset()
                        .toDp()
                }
            )
            .anchoredDraggable(
                state = draggableState,
                orientation = Orientation.Horizontal,
                enabled = true,
                reverseDirection = false,
                interactionSource = null
            )
            .padding(start = 24.dp)
    )
}
```

これらの機能を駆使することで、Jetpack Compose におけるユーザーインタラクションは一層豊かになります。機能の実装にあたっては、常にユーザビリティを考慮し、最適なユーザーエクスペリエンスを提供することが重要です。


# 終わり

この記事は、雑メモをGPT4に通して記事っぽくしてもらってます。
draggable2D, draggable もメモにあったのですが長すぎて削られた様子。