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 件のコメント:
コメントを投稿