0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Posted at

「画面に赤い〇を表示させ、ドラッグ&ドロップする」という機能を実装していきます!
役割の明確化をするため、以下のように定義します!
 ・UI: 〇を描画させる処理
 ・viewModel: 〇の位置を移動させる処理

最初にやること

viewModelを使うので、依存関係を設定しておきます。

  1. libs.versions.tomlのversionslibrariesの2か所に以下を追加
    libs.versions.toml
    [versions]
     //その他バージョン
     lifecycle-viewmodel-compose = "2.8.7"
    
    [libraries]
    //その他のライブラリ
     androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycle-viewmodel-compose" }
    
  2. build.gradle.ktsのdependenciedに以下を追加
    build.gradle.kts
    dependencies {
        //その他の依存関係
        implementation(libs.androidx.lifecycle.viewmodel.compose)
    }
    
  3. Sync Nowする ←よく忘れる💦

UIを作成

まずは、単純に画面に赤い〇を表示するだけのUIを作ります。
関数名はHogeScreen。
コードはこんな感じ。

HogeScreen.kt
@Composable
fun HogeScreen(
    modifier: Modifier = Modifier,
) {
    Canvas(
        modifier = modifier
            .fillMaxSize()
            .background(Color.LightGray),
    ) {
        drawCircle(
            color = Color.Red,
            radius = 100f,
            center = center,
        )
    }
}

〇の描画はCanvasコンポーザブルのdrawCircleを使います!
modifierでCanvas全体の表示設定をします。
とりま、fillMaxSize()でCanvasを全画面に広げ、background()で背景色を設定。
色は適当にLightGrayにしてみました。

Canvasのラムダ{}は何を描画するか?を設定するところ。
drawCircle()で〇を描画させます。
どんな〇なのかは、()の中で指定します。
適当に、色は赤、大きさは100fにしておこう。

実行すると・・・

日の丸弁当かな💦

ViewModelでボールの位置を決定

〇の出現位置は勝手に中央になっていましたが、viewModel側で決めたいと思います!
uiフォルダの中にHogeViewModel.ktファイルを作成し、HogeViewModelクラスを作成。
コードは以下のようにしました。

HogeViewModel.kt
class HogeViewModel: ViewModel() {
    //ボールの位置を保持するStateFlow
    private val _ballPosition: MutableStateFlow<Offset> = MutableStateFlow(Offset(100f, 100f))
    val ballPosition: StateFlow<Offset> = _ballPosition.asStateFlow()
}

〇の位置を_ballPositionで設定。
初期値は〇の大きさと同じ100fにしてみました!
HogeScreenに〇の位置はOffset(100f,100f)だからヨロ!と教えてあげなければいけません。
そのため、ballPosition変数をUI用に作成。

HogeScreen⇔HogeViewModel

HogeScreenとHogeViewModelを相互にやり取りするための設定を、HogeScreenに入れます。

HogeScreen.kt
import androidx.lifecycle.viewmodel.compose.viewModel

@Composable
fun HogeScreen(
    modifier: Modifier = Modifier,
+    viewModel: HogeViewModel = viewModel(),  //追加
) {
+    val ballPosition by viewModel.ballPosition.collectAsState() //追加

    Canvas(
        modifier = modifier
            .fillMaxSize()
            .background(Color.LightGray),
    ) {
        drawCircle(
            color = Color.Red,
            radius = 100f,
+            center = ballPosition, //変更
        )
    }
}

私の環境だけかもしれませんが、viewModel: HogeViewModel = viewModel()のviewModel()がいつも自動的にインポートされず、エラーになります😢
しかたがないので、import androidx.lifecycle.viewmodel.compose.viewModelを手動で記述してます。
drawCircleの'center'をviewModelからもってきたballPositionとすることにより、〇の表示位置が変わります。
実行すると。。。。

ドラッグ&ドロップしたときの処理

次はHogeScreenでドラッグ&ドロップしたときの処理を書いていきます。
画面に対してクリック(タップ)したとか、ドラック&ドロップした(スワイプ)のような情報は、modifierのpointerInput(Unit)でキャッチ。
ラムダ{}の中はどういうジェスチャーに対して処理するのか?を記述。
今回はドラッグ&ドロップをするので、detectDragGestures()を使っていきます。

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 = {},
+                    onDrag = { change, _ ->},
+                    onDragEnd = {},
+                    onDragCancel = {},
+                )
+            }
    ) {
        //省略
    }
}

'onDragStart'とかのラムダ{}は空っぽにしておく。
まだ何も処理作ってないからね^^;

viewModelにボールを動かす処理を追加

HogeViewModelにドラッグ開始位置を記録する変数dragStartOffsetを1つと、メソッド3つonDragStart onDrag onDragEnd追加します。

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
    }
}

各メソッドの役割は次の通りです。

  • onDragStart
     doragStartOffsetにドラッグ開始の位置を入れる

  • onDrag
     ドラッグによる移動量はdragAmountから取得。
    〇の位置_ballPositionを移動した分dragAmountだけ加算し、新しい位置'newPosition'を作る。
     〇の位置をnewPositionで更新。

  • onDragEnd
     ドラッグが終わったら、dragStartOffsetを空っぽにする

実行してみるとー

問題点について

実際に触ってみるとわかりますが、〇をドラッグしなくても〇は動きます。
〇とタップした位置の距離を測って、〇の範囲に入っているかどうか?を確認し、入っていればドラッグスタート!入っていなければ無視のような処理をしないとですね。
次回修正していきたいと思います!

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?