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)
他のイベントは公式参照
Viewの位置の入れ替え(swap)は、
- @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;
- }
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 件のコメント:
コメントを投稿