💝

ドラッグ&ドロップ実装2

に公開

前回の記事
https://zenn.dev/rucco/articles/f78133f0acdca3

前回のアプリを見たい方はこちら

前回のコード

HogeScreen.kt
@Composable
fun HogeScreen(
    modifier: Modifier = Modifier,
    viewModel: HogeViewModel = viewModel(),
) {
    val ballPosition by viewModel.ballPosition.collectAsState()

    Canvas(
        modifier = modifier
            .fillMaxSize()
            .background(Color.LightGray)
            .pointerInput(Unit) {
                detectDragGestures(
                    onDragStart = { offset ->
                        viewModel.onDragStart(offset)
                    },
                    onDrag = { change, amount ->
                        viewModel.onDrag(amount)
                    },
                    onDragEnd = { viewModel.onDragEnd() },
                    onDragCancel = {},
                )
            }
    ) {
        drawCircle(
            color = Color.Red,
            radius = 100f,
            center = ballPosition,
        )
    }
}
HogeViewModel.kt
class HogeViewModel: ViewModel() {
    //ボールの位置を保持するStateFlow
    private val _ballPosition: MutableStateFlow<Offset> = MutableStateFlow(Offset(100f, 100f))
    val ballPosition: StateFlow<Offset> = _ballPosition.asStateFlow()

    private var dragStartOffset: Offset? = null //ドラッグ開始の位置

    fun onDragStart(offset: Offset) {
        dragStartOffset = offset
    }

    fun onDrag(dragAmount: Offset) {
        if (dragStartOffset != null) {
            val newPosition = Offset(
                _ballPosition.value.x + dragAmount.x,
                _ballPosition.value.y + dragAmount.y
            )
            _ballPosition.update { it.copy(newPosition.x, newPosition.y) }
        }
    }

    fun onDragEnd() {
        dragStartOffset = null
    }
}

アプリのイメージ

🫡タップした場所とボールとの距離

前回まで、ボールのことを〇と表記していましたが、わかりづらいのでボールと表記します!!!
さて、ボールに触ってなくてもドラッグするとボールが動いてしまう問題に対応します。
タップとボールの距離を計算して、ボールに触っているかどうかを確認する関数をViewModelに追加します。

HogeViewModel.kt
class HogeViewModel: ViewModel() {
    //省略
    //ボールに触っているかどうか
    fun isTapOnBall(tapPosition: Offset,ballPosition: Offset): Boolean {
        val distance = (tapPosition - ballPosition).getDistance()
        return distance <= 100f
    }
}

タップの位置tapPositionからボールの位置ballPositionを引くことによって、2者間の差分(ベクトル)を計算。
'getDistance()`で2点をつないだ直線(直角三角形の斜め部分)の距離を計算。
結果がボールの半径(100f)以下であれば、ボールをタップしたと判断しtrue返す。

🫡タップした位置にボールがあるときだけドラッグ

次は、ドラッグを開始したとき、タップした位置にボールがあるときだけドラッグするようにviewModelのonDragStartメソッドを修正します。

HogeViewModel.kt
class HogeViewModel: ViewModel() {
    //省略
    fun onDragStart(offset: Offset) {
        if (isTapOnBall(offset, _ballPosition.value)) dragStartOffset = offset
    }
    //省略
}

もし、isTapOnBall関数の結果がtrueだったら、dragStartOffsetにタップの位置を設定してドラッグを描画する、という処理。
offsetにはHogeScreenからタップ開始の位置が飛んできます。

そんなわけで、実行~~♪

実行結果はこちら

うん。思った通りにドラッグ&ドロップできた!

Discussion