Androidでパズドラ風のドラッグ操作を作る機会があったのでjavaでのソースコードを残しておく
Kotlinも書こうかと思ったけど、コピペしてAndroidStudioに貼ったら自動変換されるので良しとする
こういう動き
設計
レイアウト
Viewを並べられればなんでもいいですけど、とりあえず4×4のGridLayout
画像は適当に標準で存在する画像を利用
レイアウトは記事のメインではないので、コードはURLだけで(ブログが冗長になるからね)
ドラッグ&ドロップの実装
まずは公式「ドラッグ&ドロップ」で知識を身に着ける
ただし、ここに書かれているドラッグを開始するView#startDragはAPI24から非推奨なのでView#startDragAndDropメソッドを使用すべき。
ドラッグされたViewの動作イベントはView.OnDragListenerでイベントで拾える。
ドラッグ契機となるのはImageViewのタッチなのでView.OnTouchListenerも実装。
よって、全体像とてはこんな感じ
public class MainActivity extends AppCompatActivity
implements View.OnTouchListener, View.OnDragListener {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// TODO:後で解説
}
@Override
public boolean onTouch(View v, MotionEvent motionEvent) {
// TODO:後で解説
return true;
}
@Override
public boolean onDrag(View v, DragEvent event) {
// TODO:後で解説
return true;
}
}
OnCreateの実装
Viewのリスナーをここで設定しておく。
注意が必要なのはOnDragListener登録は全部のImageViewに仕込む必要がある点。
図cのように、ドラッグ状態のViewの上を通過契機でViewの位置入れ替えを行いたいのだが、ドラッグ状態のViewにのみOnDragListenerを設定しても、他のViewの上を通過したイベントは発火しない。これは、通過される側のViewのイベントであるため、通過されるView側にもリスナーを仕込む必要があった。
よって、実装は全部のViewにOnDragListenerを仕込む。
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 各Viewに、タッチ・ドラッグのイベントを設定していく
GridLayout parent = findViewById(R.id.grid_layout);
for (int i = 0; i < parent.getChildCount(); i++) {
View v = parent.getChildAt(i);
v.setOnTouchListener(this);
v.setOnDragListener(this);
}
}
OnTouchの実装
|
タッチされたときの動作として、触ったViewをドラッグ状態にする。
ドラッグ状態すると、右図のようにドラッグ中Viewが薄く表示、元のViewはそのまま表示される。⇒ |
仕様では、図bのように元のViewは非表示にする必要があるので、元のView側の透過値を0にして見えなくする。
ここで表示可否のVisibilityを用いてView.INVISIBLEやView.GONEで消すと、見えなくできるがドロップ時にドロップに「失敗」する。このとき、元の位置に戻るアニメーションが入り動作が不自然になる。以上の理由から透過で消す方法で実現させる
/** ドラッグしたViewはメンバ変数保持(ドラッグ処理で使用) */
private View mDragView;
@Override
public boolean onTouch(View v, MotionEvent motionEvent) {
// 押下時に動作
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
mDragView = v;
// Viewをドラッグ状態にする。
v.startDragAndDrop(null, new View.DragShadowBuilder(v), v, 0);
v.setAlpha(0);
}
return true;
}
onDragの実装
ドラッグイベントは6種類あるが、使用するのは以下の2つ
- 手を放し、ドラッグが終了したイベント(ACTION_DRAG_ENDED)
- ドラッグ中に他のViewの上に乗ったイベント(ACTION_DRAG_LOCATION)
他のイベントは公式参照
@Override
public boolean onDrag(View v, DragEvent event) {
switch (event.getAction()) {
// 手を放し、ドラッグが終了した時の処理
// ドラッグしているViewを表示させる。
case DragEvent.ACTION_DRAG_ENDED:
getMainExecutor().execute(() -> mDragView.setAlpha(1));
break;
// ドラッグ中他のViewの上に乗る時の処理
// Viewの位置を入れ替える
case DragEvent.ACTION_DRAG_LOCATION:
getMainExecutor().execute(() -> swap(v, mDragView));
break;
}
return true;
}
Viewの位置の入れ替え(swap)は、LayoutParamを入れ替えればよいので以下のようになる。
private void swap(View v1, View v2) {
// 同じViewなら入れ替える必要なし
if (v1 == v2) return;
GridLayout parent = findViewById(R.id.grid_layout);
// レイアウトパラメータを抜き出して、入れ替えを行う
GridLayout.LayoutParams p1, p2;
p1 = (GridLayout.LayoutParams) v1.getLayoutParams();
p2 = (GridLayout.LayoutParams) v2.getLayoutParams();
parent.removeView(v1);
parent.removeView(v2);
parent.addView(v1, p2);
parent.addView(v2, p1);
}
完成!
最後に
最終的に、メンバ変数とかコードがバラバラになってるので、GitHubにまとめておく



0 件のコメント:
コメントを投稿