ドラッグ&ドロップの実装1
最初にやること
viewModelを使うので、依存関係を設定しておきます。
ファイルの場所はこちら
- libs.versions.tomlの
versions
とlibraries
の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" }
- build.gradle.ktsの
dependencied
に以下を追加build.gradle.ktsdependencies { //その他の依存関係 implementation(libs.androidx.lifecycle.viewmodel.compose) }
- Sync Nowする ←よく忘れる💦
UIを作成
まずは、単純に画面に赤い〇を表示するだけのUIを作ります。
関数名はHogeScreen。
コードはこんな感じ。
Canvasのimportはこちら
import androidx.compose.foundation.Canvas
@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クラスを作成。
コードは以下のようにしました。
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に入れます。
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()を使っていきます。
@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
追加します。
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
を空っぽにする
ViewModelで作った関数をUIから動かす!
それぞれ、HogeScreenに作ったonDragStartとか、onDrag、onDaragEndにviewModelの関数を当てはめていきます。
//省略
Canvas(
modifier = modifier
.fillMaxSize()
.background(Color.LightGray)
.pointerInput(Unit) {
detectDragGestures(
+ onDragStart = { offset ->
+ viewModel.onDragStart(offset)
+ },
+ onDrag = { change, dragAmount ->
+ viewModel.onDrag(dragAmount)
+ },
+ onDragEnd = { viewModel.onDragEnd() },
+ onDragCancel = {},
+ )
}
) {
//省略
onDragStart
のoffsetはクリックした位置です。
onDrag
のchangeはドラッグにより移動したマウス(指?)の位置
dragAmountはドラッグによる移動量
viewModelに渡すのは移動量の方です。
onDragEndは何も渡さずそのまま実行させます。
実行結果はこちら
問題点について
実際に触ってみるとわかりますが、〇をドラッグしなくても〇は動きます。
〇とタップした位置の距離を測って、〇の範囲に入っているかどうか?を確認し、入っていればドラッグスタート!入っていなければ無視のような処理をしないとですね。
次回修正していきたいと思います!
Discussion