2010/11/20

Google Maps でダブルタップズーム for Android

Android で Map を扱うアプリを作っていると、デフォルトで入っている Google Map のようにダブルタップでズームして欲しいと思います。
ところが、なかなかこれが難しいのです。
調べてみると、皆さんなかなか苦労しているようです。

Android: MapActivityでダブルタップする (パトラッシュさん)

そこで、もう少しシンプルなやり方が無いか考えてみました。
ポイントは、onDoubleTapEvent ではなく onDoubleTap を使うことと、ZoomControl を連続タップした時にダブルタップとして認識されないようにする方法です。

import com.google.android.maps.MapActivity;
import com.google.android.maps.MapView;

import android.graphics.Rect;
import android.os.Bundle;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.GestureDetector.OnDoubleTapListener;
import android.view.GestureDetector.OnGestureListener;

public class Main extends MapActivity implements OnGestureListener, OnDoubleTapListener {
    private MapView mapView;
    private GestureDetector gestureDetector;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mapView = (MapView)findViewById(R.id.mapView01);
        mapView.setBuiltInZoomControls(true);

        gestureDetector = new GestureDetector(this, this);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent e) {
        super.dispatchTouchEvent(e);

        // ZoomController の 画面上の座標を取得する
        int[] offset = new int[2];
        mapView.getZoomButtonsController().getContainer().getLocationOnScreen(offset);
        // ZoomController 中の Hit領域の矩形を取得する
        // (Hitとは領域は + - が表示されている領域のこと)
        Rect rect = new Rect();
        mapView.getZoomButtonsController().getZoomControls().getHitRect(rect);
        // 画面上の座標に変換
        rect.offset(offset[0], offset[1]);
        if (!rect.contains((int)e.getRawX(), (int)e.getRawY())) {
            // タッチされた場所が、Hit領域に含まれていなければ
            gestureDetector.onTouchEvent(e);
        }

        return onTouchEvent(e);
    }

    public boolean onDoubleTap(MotionEvent e) {
        // ズーム処理
        mapView.getController().zoomInFixing((int)e.getX(), (int)e.getY());
        return true;
    }

    // ・・・
    // その他の 自動生成されたメソッド・スタブ に関しては省略

}

以下、簡単な解説です。

  1. MapActivity がダブルタップイベントを取得できるようにする
    OnGestureListener, OnDoubleTapListenerimplements することで、Activity が複雑なイベントを取得できるようになります。
    しかし、このままでは MapView がイベントを先取りしてしまうので、その前に dispatchTouchEventOverride して、イベントを先に横取りする必要があります。

    この方法は、特定のタップイベント時にMapViewから座標を取得する (名無しの備忘録さん)のサイトが参考になりました

  2. DispatchTouchEvent の中身を書く
    最もシンプルな DispatchTouchEvent は、以下のように書けば良いようです
    @Override
        public boolean dispatchTouchEvent(MotionEvent e) {
            super.dispatchTouchEvent(e);
            gestureDetector.onTouchEvent(e);
            return onTouchEvent(e);
        }
    

  3. ダブルタップ時の動作を書く
    onDoubleTap(MotionEvent e) の中にズーム処理を書きます。
    ちなみに、onDoubleTapEvent(MotionEvent e) というイベントもあります。
    これらの違いについては、 onDoubleTap と onDoubleTapEvent の違い に書いてみましたので参考にしてください。(2010/11/28 追記)

  4. ZoomControl を連続タップした時にはズームしないようにする
    このままだと、ZoomControl を連続タップしたとき、ZoomControl 位置にズームしていってしまいます。-(マイナス)ボタンを連打したときは、不思議な動きをして中々面白いですw
    しかし、これでは実用になりませんので、ZoomControl 領域をタップした時だけは、ダブルタップズームを無効にします。
    Android: MapActivityでダブルタップする (パトラッシュさん)のサイトでは、ZoomControl のタップイベントでフラグを立ててこれを回避していますが、私は領域で排除する方法をとりました。
    それが、dispatchTouchEvent の中でゴチャゴチャやっているやつです。
    ぱっと見、複雑ですが、ZoomControl の領域を rect に保存して、タップした点がその中に含まれているかどうかを判定しています。

0 件のコメント: