Androidでドラッグ・アンド・ドロップ

Canvasを使わないでドラッグアンドドロップする方法
Canvasを使わない利点

  • layoutをxmlでかける
  • onDrawとか使わなくて済む
  • どんなViewでも動かせる
  • Viewのイベントを発生することができる。(アニメーションの連携とか)
  • Viewを重ねることができる
  • 重なったViewでイベントを発行できる(OnCLickとか)

Tip

  • FrameLayoutでVIewを重ねる

※実際他のLayoutでもできるけど、こっちの方が実装しやすい

  • Viewの絶対座標はView#getGrobalRectで取得
  • TouchEventリスナーは動かしたいViewだけ実装
  • タッチポイントの絶対座標はgetRowX,Y
  • Drag中の描画座標は前回の座標の差分を計算
  • Viewの表示位置はView#layoutで設定
ついでにこんなこともしてみる
  • dropしたときにアニメーション
    • res/anim/sample.xmlを作成する
  • dropしたところにButtonがあったらonClick
    • getHitRectでButtanの有効座標を取得する
    • rect#containでタッチ座標が有効座標に含まれているか判定
    • onCLickの実行はperformClick
//Dropポイントにボタンがあったら、onClick
bt2.getHitRect(rect);
if (rect.contains(x, y)){
	bt2.performClick();
}

サンプルアプリ

  • ドラッグアンドドロップ可能なImageViewを作成
  • Viewにアニメーションを設定
  • dropのタイミングでアニメーション実行
  • ドロップしたところにボタンがあったらonClickする
    • ドロップのタイミングでperformClickを実行するとアニメーション中に画面遷移するので、Animationリスナーを実装して、アニメーション終了のタイミングでperformClickを実行する

ソース

■main.xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical" android:layout_width="fill_parent"
	android:layout_height="fill_parent" android:id="@+id/layout">

	<FrameLayout android:id="@+id/FrameLayout01"
		android:background="#333333" android:layout_height="wrap_content"
		android:layout_width="fill_parent" android:layout_weight="10">
		<Button android:id="@+id/Button02" android:layout_width="wrap_content"
			android:layout_height="wrap_content" android:layout_gravity="top|center"
			android:text="Button2" android:onClick="onClickButton02" android:width="150dp" android:height="150dp"></Button>
		<ImageView android:id="@+id/ImageView01"
			android:layout_width="wrap_content" android:layout_height="wrap_content"></ImageView>
			
	</FrameLayout>
	<LinearLayout android:orientation="vertical" android:id="@+id/layout"
		android:layout_weight="1" android:layout_height="wrap_content"
		android:layout_width="fill_parent">

		<Button android:id="@+id/Button01" android:layout_height="wrap_content"
			android:text="Show" android:layout_width="fill_parent"
			android:onClick="onClickShowButton"></Button>
		<TextView android:layout_width="wrap_content"
			android:layout_height="wrap_content" android:id="@+id/TextViewPoint"
			android:text="x: y:"></TextView>

	</LinearLayout>
</LinearLayout>

■sample.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    >
<rotate
	android:fromDegrees="0"
	android:toDegrees="360"
	android:toYScale="0.0"
	android:pivotX="50%"
	android:pivotY="50%"
	android:duration="400"
	android:fillBefore="false"
	android:fillAfter="false"
/>
</set>


■main.java

package com.dd.sample;

import android.app.Activity;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.Animation.AnimationListener;
import android.widget.Button;
import android.widget.ImageView;

public class DragAndDropSample extends Activity implements OnTouchListener {
	ImageView target;
	private Animation anime;
	int startX;
	int startY;
	int currentX;
	int currentY;
	int offsetX;
	int offsetY;
	private Rect rect = new Rect();
	private Button bt2;
	private boolean isBt2Click;

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		Resources r = getResources();
		bt2 = (Button) findViewById(R.id.Button02);
		Bitmap bmp = BitmapFactory.decodeResource(r, R.drawable.android);
		anime = AnimationUtils.loadAnimation(this, R.anim.sample);
		anime.setAnimationListener(new AnimationListener() {

			@Override
			public void onAnimationStart(Animation animation) {
			}

			@Override
			public void onAnimationRepeat(Animation animation) {
			}

			@Override
			public void onAnimationEnd(Animation animation) {
				if (isBt2Click) {
					bt2.performClick();
					isBt2Click = false;
				}
			}
		});

		target = (ImageView) findViewById(R.id.ImageView01);
		target.setImageBitmap(bmp);
		this.target.setOnTouchListener(this);

	}

	public void onClickShowButton(View v) {

		Rect rect = new Rect();
		Point globalOffset = new Point();
		target.getGlobalVisibleRect(rect, globalOffset);
		currentX = startX;
		currentY = startY;
		target.layout(currentX, currentY, currentX + target.getWidth(),
				currentY + target.getHeight());
		target.setVisibility(View.VISIBLE);
	}

	public void onClickButton02(View v) {
		// Toast.makeText(this, "click", Toast.LENGTH_SHORT).show();
		Intent intent = new Intent(DragAndDropSample.this, Next.class);
		startActivity(intent);
	}

	@Override
	public boolean onTouch(View v, MotionEvent event) {
		int x = (int) event.getRawX();
		int y = (int) event.getRawY();

		if (event.getAction() == MotionEvent.ACTION_MOVE) {

			int diffX = offsetX - x;
			int diffY = offsetY - y;

			currentX -= diffX;
			currentY -= diffY;
			target.layout(currentX, currentY, currentX + target.getWidth(),
					currentY + target.getHeight());

			offsetX = x;
			offsetY = y;

		} else if (event.getAction() == MotionEvent.ACTION_DOWN) {
			offsetX = x;
			offsetY = y;
		} else if (event.getAction() == MotionEvent.ACTION_UP) {
			target.setAnimation(anime);
			target.startAnimation(anime);
			target.setVisibility(View.GONE);

			bt2.getHitRect(rect);
			if (rect.contains(x, y)) {
				isBt2Click = true;
			}
		}

		return true;
	}
}