ScaleGestureDetectorを使ってピンチイン、ピンチアウトを認識する


最近のAndroid端末はマルチタップに対応しているものが多いです。Android2.2(API 8)からはマルチタッチに対応したモーションジェスチャークラスも追加されました。

今回は、その2.2から追加されたScaleGestureDetectorクラスでマルチタップならではのピンチイン、ピンチアウトを認識する方法を説明します。

それでは続きへどうぞ

カスタムView

まずカスタムViewを用意する必要があります。カスタムViewがScaleGestureDetectorクラスのインスタンスを保持しておき、onTouchEventメソッドが呼ばれた際に、ScaleGestureDetector#onTouchEventを呼ぶことでマルチタッチの認識を行います。

private ScaleGestureDetector _gestureDetector;

@Override
public boolean onTouchEvent(MotionEvent ev) {
  _gestureDetector.onTouchEvent(ev);
  return true;
}

ScaleGestureDetectorクラス

ScaleGestureDetector(Context context, ScaleGestureDetector.OnScaleGestureListener listener)

コンストラクタの第1引数にはコンテキストを、第2引数にはScaleGestureDetector.OnScaleGestureListenerインタフェースを実装したクラスのインスタンスを与えます。

カスタムViewがScaleGestureDetector.OnScaleGestureListenerインタフェースを実装しても良いですし、ScaleGestureDetector.SimpleOnScaleGestureListenerクラスを用いる方法もあります。

※SimpleOnScaleGestureListenerクラスはOnScaleGestureListenerインタフェースを実装しています。

サンプルコードでは後者を採用しました。

private SimpleOnScaleGestureListener _simpleListener
		= new ScaleGestureDetector.SimpleOnScaleGestureListener() {
		 省略
		 };

public GestureDetectView(Context context) {
  super(context);
  _gestureDetector = new ScaleGestureDetector(context, _simpleListener);
}

SimpleOnScaleGestureListenerインタフェース

3つのメソッドを実装する必要があります。

  • abstract boolean onScale(ScaleGestureDetector detector)
  • abstract boolean onScaleBegin(ScaleGestureDetector detector)
  • abstract void onScaleEnd(ScaleGestureDetector detector)

それぞれジェスチャー中、ジェスチャーの開始、ジェスチャーの終了に呼ばれるメソッドです。引数で受け取るScaleGestureDetectorクラスから情報を取得して任意の処理を行います。

ジェスチャーの認識

ジェスチャーが行われた時は上記のようにリスナのメソッドが呼ばれます。

そこでScaleGestureDetector#getScaleFactorを呼び出しピンチアウトが行われたのか、ピンチインが行われたのかを判断することができます。得られる値は前回のイベントでの2点間の距離との比率です。1.0より大きい場合は前回のイベントより2点間の距離が大きくなった=ピンチアウトが行われことになります。1.0より小さい場合はピンチインが行われたと判断することができます。

前回のイベントの比率だけでなくScaleGestureDetector#getCurrentSpanXScaleGestureDetector#getCurrentSpanYを用いることで、その時のX軸とY軸の2点間の距離を得ることができます。

サンプルコード

今回はViewクラスを継承したGestureDetectViewクラスを用意して、ピンチアウトで画像の拡大、ピンチインで縮小を行うサンプルコードを用意しました。

public class GestureDetectView extends View {
	private static final String TAG = "GestureDetectView";
	private ScaleGestureDetector _gestureDetector;

	private Drawable _image;
	private float _scaleFactor = 1.0f;

    @Override
    public void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.save();
        canvas.scale(_scaleFactor, _scaleFactor);
        _image.draw(canvas);
        canvas.restore();
    }

_imageで用意している画像を_scaleFactorで保持している値に拡大(縮小)して表示します。

コンストラクタでScaleGestureDetectorのインスタンスを生成します。

	public GestureDetectView(Context context) {
		super(context);
        _image = context.getResources().getDrawable(R.drawable.tb);
        _image.setBounds(0, 0, _image.getIntrinsicWidth(), _image.getIntrinsicHeight());
		_gestureDetector = new ScaleGestureDetector(context, _simpleListener);
	}

そしてその第2引数には_scaleFactorの値を更新して再描画を行うように記述したSimpleOnScaleGestureListenerを与えます。

	private SimpleOnScaleGestureListener _simpleListener
		= new ScaleGestureDetector.SimpleOnScaleGestureListener() {
			@Override
			public boolean onScaleBegin(ScaleGestureDetector detector) {
				Log.d(TAG, "onScaleBegin : "+ detector.getScaleFactor());
				invalidate();
				return super.onScaleBegin(detector);
			}

			@Override
			public void onScaleEnd(ScaleGestureDetector detector) {
				Log.d(TAG, "onScaleEnd : "+ detector.getScaleFactor());
				_scaleFactor *= detector.getScaleFactor();
				invalidate();
				super.onScaleEnd(detector);
			}

			@Override
			public boolean onScale(ScaleGestureDetector detector) {
				Log.d(TAG, "onScale : "+ detector.getScaleFactor());
				_scaleFactor *= detector.getScaleFactor();
				invalidate();
				return true;
			};
		};

今回のサンプルコードはこちらです。