タッチしたオブジェクトを指で動かしました。

2012.06.08

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

以下の動きを目標にしました。

  • 指にViewコンポーネント(画像)がついてくる
  • 画面の右下のゴミ箱に画像をドラッグすると消える
  • 一度ゴミ箱にドラッグした後、ゴミ箱をクリックしたら画面左上に画像を表示する

main.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
	android:id="@+id/FrameLayout01"
	android:layout_height="match_parent"
	android:layout_width="match_parent"
	android:layout_weight="10"
	android:background="@color/white">

	<Button
		android:layout_gravity="bottom|right"
		android:layout_width="30dp"
		android:layout_height="30dp"
		android:id="@+id/trash"
		android:background="@drawable/custom_button"
		/>

	<ImageView
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:src="@drawable/move"
		android:id="@+id/ImageView01" />

</FrameLayout>

MainActivity.java

package com.sample.movetest;

import android.app.Activity;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;

public class MainActivity extends Activity
	implements OnTouchListener, OnClickListener {

	private FrameLayout frameLayout01;
	private ImageView target;
	private Button trash;

	private int targetLocalX;
	private int targetLocalY;

	private int screenX;
	private int screenY;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        frameLayout01 = (FrameLayout)findViewById(R.id.FrameLayout01);

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

        trash = (Button)findViewById(R.id.trash);
        trash.setOnClickListener(this);
    }

	@Override
	public boolean onTouch(View v, MotionEvent event) {

		int x = (int)event.getRawX();
		int y = (int)event.getRawY();

		switch(event.getAction()) {
		case MotionEvent.ACTION_DOWN:

			targetLocalX = target.getLeft();
			targetLocalY = target.getTop();

			screenX = x;
			screenY = y;

			break;

		case MotionEvent.ACTION_MOVE:

			int diffX = screenX - x;
			int diffY = screenY - y;

			targetLocalX -= diffX;
			targetLocalY -= diffY;

			target.layout(targetLocalX,
						  targetLocalY,
						  targetLocalX + target.getWidth(),
						  targetLocalY + target.getHeight());

			screenX = x;
			screenY = y;

			break;

		case MotionEvent.ACTION_UP:

			int trashLeft    = trash.getLeft() + trash.getWidth()/2;
			int trashTop     = trash.getTop()  + trash.getHeight()/2;
			int targetRight  = target.getLeft() + target.getWidth();
			int targetBottom = target.getTop() + target.getHeight();

			if (targetRight > trashLeft && targetBottom > trashTop) {
				frameLayout01.removeView(target);
			}
			break;
		}
		return true;
	}

	@Override
	public void onClick(View v) {
		int childCount = frameLayout01.getChildCount();
		if(childCount == 1) {
			frameLayout01.addView(target);
		}
	}
}

■使用するレイアウト

ゴミ箱に画像をドラッグする(重ねる)ので 使用するレイアウトは、FrameLayoutを使いました。 FrameLayoutは、複数のViewを重ねて表示することができます。 位置を何も指定しない場合は、左上に表示されます。 また、後から配置したViewが前面に配置されます。 FrameLayoutの他にRelativeLayoutもViewを重ねて表示することが できます。

■動かす対象Viewのタッチイベントを検知して処理する

MainActivity.javaの14行目

implements OnTouchListener, OnClickListener {

オブジェクトごとのタッチイベントの検知は、 OnTouchListenerインターフェースを実装します。

MainActivity.javaの33行目

target.setOnTouchListener(this);

動かす対象Viewにタッチイベントのリスナー登録をします。

MainActivity.javaの40~87行目

@Override
public boolean onTouch(View v, MotionEvent event) {
…
}

onTouchというコールバックメソッドをオーバーライドします。 Viewを指で動かした時のタッチイベントを検知したいので今回はonTouchを使います。 また、ViewごとでなくActivity全体のタッチイベントの検知は、 onTouchEvent()コールバックメソッドをオーバーライドします。

MainActivity.javaの42~43行目

int x = (int)event.getRawX();
int y = (int)event.getRawY();

タッチした場所の絶対座標を取得します。 絶対座標を取得するにはgetRawX(),getRawY()を使用しますが、 相対座標を取得する場合はgetX(),getY()を使用します。

MainActivity.javaの45~85行目

switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
…
}

case MotionEvent.ACTION_DOWN: → 押したとき、 case MotionEvent.ACTION_MOVE: → 動かしたとき、 case MotionEvent.ACTION_UP: → 離したときと タッチイベントのアクションごとに処理をわけます。 アクションはDOWN→MOVE→UPの順番で発生します。 今回は使用していないですが、他には ACTION_CANCEL(タッチがキャンセルされたとき) ACTION_OUTSIDE(対象のViewの範囲外をタッチしたとき)があります。

MainActivity.javaの46~54行目

case MotionEvent.ACTION_DOWN:

	targetLocalX = target.getLeft();
	targetLocalY = target.getTop();

	screenX = x;
	screenY = y;

	break;
押したときの処理

対象Viewの左端座標を親Viewの相対座標として取得し、targetLocalXに入れます。 対象Viewの上端座標を親Viewの相対座標として取得し、targetLocalYに入れます。 タッチした場所のx座標をscreenXに入れます。 タッチした場所のy座標をscreenXに入れます。

MainActivity.javaの56~72行目

case MotionEvent.ACTION_MOVE:

	int diffX = screenX - x;
	int diffY = screenY - y;

	targetLocalX -= diffX;
	targetLocalY -= diffY;

	target.layout(targetLocalX,
				  targetLocalY,
				  targetLocalX + target.getWidth(),
				  targetLocalY + target.getHeight());

	screenX = x;
	screenY = y;

	break;
動かしたときの処理

押したときの座標と動かしたときの座標の差分をdiffX,diffYに入れます。 targetLocalX,targetLocalYに差分を反映させます。 差分を反映させた位置に対象Viewを移動させます。 タッチした場所のx座標をscreenXに入れます。 タッチした場所のy座標をscreenXに入れます。

MainActivity.javaの74~85行目

case MotionEvent.ACTION_UP:

	int trashLeft    = trash.getLeft() + trash.getWidth()/2;
	int trashTop     = trash.getTop()  + trash.getHeight()/2;
	int targetRight  = target.getLeft() + target.getWidth();
	int targetBottom = target.getTop() + target.getHeight();

	if (targetRight > trashLeft && targetBottom > trashTop) {
		frameLayout01.removeView(target);
	}
	break;
離したときの処理

ゴミ箱に入れたと判定する為の位置座標をtrashLeft,trashTopに入れます。 対象の右端座標、下端座標がtrashLeft,trashTop超えた場合は 対象Viewを削除します。

■ゴミ箱をクリックしたときの処理

MainActivity.javaの14行目

implements OnTouchListener, OnClickListener {

クリックイベントの検知は、OnClickListenerインターフェースを実装します。

MainActivity.javaの36行目

trash.setOnClickListener(this);

ゴミ箱用のコンポーネントににクリックイベントのリスナー登録をします。

MainActivity.javaの92~94行目

if(childView == null) {
	frameLayout01.addView(target);
}

動かす対象Viewが画面にない場合は、表示させます。