2010年2月14日日曜日

特定のタップイベント時にMapViewから座標を取得する

意外と試行錯誤したので記録にしておきます。

というワケでまずは特定のタップイベントの取得に関して・・・。
は、以下のサイトを参考にしました。

hoge256blogさん
adakodaさん

上記のサイトを見て、なんも考えずに
・Activity → MapActivity
ぐらいの変更でとりあえずなんとかなるだろう・・・。
ってのがまず甘かった。


public class Main extends MapActivity implements
GestureDetector.OnGestureListener,
GestureDetector.OnDoubleTapListener {

 private GestureDetector gesture;
 private MapView mapView;

 @Override
 public void onCreate(Bundle savedInstanceState) {
  protected void onCreate(Bundle icicle) {
   // TODO 自動生成されたメソッド・スタブ
   super.onCreate(icicle);
   setContentView(R.layout.main);

   mapView = (MapView)findViewById(R.id.map);
   mapView.setEnabled(true);
   mapView.setClickable(true);
   mapView.setBuiltInZoomControls(true);
   mapView.setSatellite(false);

   gesture = new GestureDetector(this);

   mapView.invalidate();

  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
   gesture.onTouchEvent(event);
   return super.onTouchEvent(event);
  }
  // 以下onDoubleTapとか軒並み略

いわずともあれです。
MapView上をタップしても「onTouchEvent」にイベントが来ません。
上記には書いていませんが、ところ狭しとログかけまくってます。
MapViewが全て処理してくれているため、当たり前といえば当たり前です。

じゃあ、上記mapViewになんかタッチを処理しそうなリスナーとか登録すりゃいいんじゃね?
と考え、eclipseの力を発揮(?)させ、setOnTouchListenerなるものを発見。
ってことで以下に修正。

public class Main extends MapActivity implements
GestureDetector.OnGestureListener,
GestureDetector.OnDoubleTapListener {

 private GestureDetector gesture;
 private MapView mapView;

 @Override
 public void onCreate(Bundle savedInstanceState) {
  protected void onCreate(Bundle icicle) {
   // TODO 自動生成されたメソッド・スタブ
   super.onCreate(icicle);
   setContentView(R.layout.main);

   mapView = (MapView)findViewById(R.id.map);
   mapView.setEnabled(true);
   mapView.setClickable(true);
   mapView.setBuiltInZoomControls(true);
   mapView.setSatellite(false);

   gesture = new GestureDetector(this);

   // 追加
   mapView.setOnTouchListener( new OnTouchListener() {

    public boolean onTouch(View v, MotionEvent event) {
     // TODO 自動生成されたメソッド・スタブ
     gesture.onTouchEvent(event);
     return false;
    }
   });

   mapView.invalidate();

  }

  /* コメントアウト
  @Override
  public boolean onTouchEvent(MotionEvent event) {
   gesture.onTouchEvent(event);
   return super.onTouchEvent(event);
  }
   */
  // 以下onDoubleTapとか軒並み略

結果、一回しかイベント取れないし。
ちょっとやってみたところ、どうやら「onTouch」で一度でもfalseを返すともうダメ。
普通false返したら、superクラスというか、そもそも備えた処理してくれるんじゃないんですか。
自前で処理を全部書いて、全てtrueを返すなら運用出来そうですが、MapViewですよ。
もともとある機能の分まで実装できるかっての。
ていうかできるくらい能力ありゃ苦労しませんよねw

そこで考えたのが、「流石にイベントが来ていない訳ないだろ」ということ。
横取りしちまえwwww ってこの辺の発想はVCのサブクラス化とかから来てるのかな・・・。
ということで次のように修正。

//略

 @Override
 public boolean dispatchTouchEvent(MotionEvent ev) {
  //TODO 自動生成されたメソッド・スタブ
  gesture.onTouchEvent(ev);
  return super.dispatchTouchEvent(ev);
 }

 @Override
 public void onCreate(Bundle savedInstanceState) {

  //略

  /* コメントアウト
  //追加
  mapView.setOnTouchListener( new OnTouchListener() {

   public boolean onTouch(View v, MotionEvent event) {
    //TODO 自動生成されたメソッド・スタブ
    gesture.onTouchEvent(event);
    return false;
   }
  });
   */
  mapView.invalidate();

 }

 //以下onDoubleTapとか軒並み略

あくまでイベントが知りたいだけなので、処理するしないに関わらず、本来の処理に渡しています。
こうすることで

  • イベント取れる
  • MapViewそのものの処理は邪魔しない(はず)
と考えました。

だが幸せは長く続かなかった

とりあえず長押し処理に何かを付け加えましょう。
わかりやすいので長押しした場所を中央に移動するとかにしました。

public void onLongPress(MotionEvent e) {
 // TODO 自動生成されたメソッド・スタブ
 // 以下を追加
 // タップされた画面座標をmapView上の緯度経度に変換
 GeoPoint temp = mapView.getProjection().fromPixels((int)e.getX(), (int)e.getY());
 _log("GeoPoint lat:" + temp.getLatitudeE6() + " long:" + temp.getLongitudeE6());
 mapView.getController().animateTo(temp);
 mapView.postInvalidate();
}

こちらも一見良さそうですが、良く考えるとmapViewに渡している画面座標って、たぶんMapActivityが管理している幅になりますよね。
当然これ動作させると、なんだか長押しした場所がView中央よりやや北側になります。
ちなみに以下のような画面ならなおのこと・・・。

この場合富士山を長押ししたところで、富士山がView中央にくることはない。


じゃあ、あとは何があるかと考えると、
・MapViewを拡張する
くらいかなと思っていたところでこんなのを発見
日本アンドロイドの会
該当ページの一番下の方で「タップされた位置にアイコンを描画する」って項目があるじゃないですか。
しかも後で気がついたんですがAndroidプログラミング入門にもそれっぽいこと書いてあるし・・・。一体どこ読んでんだ?っていわれそうです。
ってことでOverlayを試してみましょう。

public class Main extends MapActivity {

 private MapView mapView;

 @Override
 public void onCreate(Bundle savedInstanceState) {
  protected void onCreate(Bundle icicle) {
   // TODO 自動生成されたメソッド・スタブ
   super.onCreate(icicle);
   setContentView(R.layout.main);

   mapView = (MapView)findViewById(R.id.map);
   mapView.setEnabled(true);
   mapView.setClickable(true);
   mapView.setBuiltInZoomControls(true);
   mapView.setSatellite(false);
   mapView.getOverlays().add( new MyOverlay(mapView));
   mapView.invalidate();

  }

  // 略
 }

 /**
  * Overlayクラス追加
  */
 class MyOverlay extends Overlay implements
 GestureDetector.OnDoubleTapListener,
 GestureDetector.OnGestureListener{

  private GestureDetector gesture = new GestureDetector(this);
  private MapView parent;

  MyOverlay(MapView mapView){
   parent = mapView;
  }

  @Override
  public boolean onTouchEvent(MotionEvent e, MapView mapView) {
   // TODO 自動生成されたメソッド・スタブ
   gesture.onTouchEvent(e);
   return super.onTouchEvent(e, mapView);
  }


  // 略

  public void onLongPress(MotionEvent e) {
   // TODO 自動生成されたメソッド・スタブ
   GeoPoint temp = parent.getProjection().fromPixels((int)e.getX(), (int)e.getY());
   parent.getController().animateTo(temp);
   parent.postInvalidate();

  }

  // 略

 }

これで無事MapView上で長押しした場所が、Viewの真ん中に来ることになりました。
まさかOverlayにこんな使い方があるとは・・・。
てっきり描画だけかと思っていましたし。
いや、正しい使い方かどうかは分かりませんがね。
まあ、考え方によってはイベント処理のみを追加したい場合に、こんなOverlayクラスを追加するだけでできるので、既存の処理への影響が少ない!かもしれないというメリットがありますかね?

0 件のコメント:

コメントを投稿