スポンサーリンク

Androidアプリで,Google Mapsの地図上にアイコン画像を配置し,そのTapイベントに反応するサンプルコード

重要なお知らせ:

この記事で公開した情報は,AndroidのMVCフレームワーク「Android-MVC」の機能の一部として取り込まれました。

より正確な設計情報や,動作可能な全ソースコードを閲覧したい場合,「Android-MVC」の公式ページより技術情報を参照してください。


AndroidのMVCフレームワーク - 「Android-MVC」
http://code.google.com/p/android-mvc-...


AndroidアプリでMapViewを使った地図アプリを作っている場合,

地図上の特定の座標に独自の「マーカー」(画像アイコンによるボタン)を配置し,

そのクリック(タップ)イベントを拾いたい場合がある。


そのサンプルコード。

編集が必要なのは,たった4ファイルのみ。



Androidアプリ上でのGoogle Mapの基本的な使い方は,下記エントリを参照。

Androidアプリで,Google Maps API+GPS+Geocoderを使って,現在地の地図と地名を表示させよう
http://language-and-engineering.hatenablog.jp/entry/20110828/p1

上記のエントリに加えて,Overlayの使い方を知りたい場合に,下記の情報が役立つ。


サンプルコード


新規Androidプロジェクトを作成。

名前はMapItemTestとする。

ターゲットとしてGoogle APIを含むものを選択。


下記のクラスを作成。

MapItemTestActivity.java:

package com.example;

import java.util.List;
import com.example.common.UserIconsOverlay;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;
import com.google.android.maps.MyLocationOverlay;
import com.google.android.maps.Overlay;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.location.LocationManager;
import android.os.Bundle;
import android.util.Log;

/**
 * マップ画面
 *
 */
public class MapItemTestActivity extends MapActivity
{

  private MapView map = null;
  private MapController mMapController;
  private MyLocationOverlay myloc_overlay;
  private UserIconsOverlay users_overlay;


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


  public void onResume()
  {
    super.onResume();

    // onCreate内だと,他の画面から戻ってきたときにマップが描画されないので
    setupMap();
  }


  // マップをセットアップ
  private void setupMap()
  {
    map = (MapView)findViewById(R.id.map_view);
    mMapController = map.getController();
    mMapController.setZoom(15);


    // アイコンを表示するオーバレイ

    // リサイズしない場合
    //Drawable drawable_icon = this.getResources().getDrawable(R.drawable.icon);

    // リサイズする場合
    Resources res = this.getResources();
    Bitmap bmp_orig = BitmapFactory.decodeResource(res, R.drawable.icon);
    Bitmap bmp_resized = Bitmap.createScaledBitmap(bmp_orig, 50, 50, false);
    Drawable drawable_icon = new BitmapDrawable(bmp_resized);

    users_overlay = new UserIconsOverlay(drawable_icon, this);
    List<Overlay> overlays = map.getOverlays();
    overlays.add(users_overlay);


    // GPSの位置情報の変更を監視するオーバレイ
    myloc_overlay = new MyLocationOverlay(this, map);
    myloc_overlay.onProviderEnabled(LocationManager.GPS_PROVIDER);
    myloc_overlay.enableMyLocation();
    //final Context ctx = this;
    myloc_overlay.runOnFirstFix(new Runnable() {
      @Override
      public void run() {

        // NOTE:画面を開いてからこのメソッドが呼ばれるまで,10秒以上待つ場合がある。

        // マップ上で新たな現在位置へ移動
        GeoPoint g = myloc_overlay.getMyLocation();
        map.getController().animateTo(g);
        map.getController().setCenter(g);

        // ダミーユーザのアイコンを表示
        users_overlay.draw_dummy_icons(g);

        Log.d("GPS操作", "位置の更新を検知");
      }
    });
    map.getOverlays().add(myloc_overlay);

    map.setBuiltInZoomControls(true);

    // 再描画
    map.invalidate();
  }


  @Override
  protected boolean isRouteDisplayed() {
    return false;
  }


  @Override
  protected void onDestroy() {
    // 破棄
    myloc_overlay.disableMyLocation();
    map.getOverlays().remove(myloc_overlay);
    map.getOverlays().remove(users_overlay);
    super.onDestroy();
  }


}


UserIconsOverlay.java

package com.example.common;

import java.util.ArrayList;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.drawable.Drawable;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.ItemizedOverlay;
import com.google.android.maps.OverlayItem;

/**
 * マップ上に独自アイコンを表示するためのオーバレイ
 *
 */
public class UserIconsOverlay extends ItemizedOverlay<OverlayItem> {

  private ArrayList<OverlayItem> overlay_items = new ArrayList<OverlayItem>();
  private Context context;


  /* --------- 基本 -------- */

  public UserIconsOverlay(Drawable defaultMarker, Context context) {
      super(boundCenterBottom(defaultMarker));
      this.context = context;

      // コンストラクタ内でpopulate() しないと,NullPointerExceptionになるので。
      // http://developmentality.wordpress.com/2009/10/19/android-itemizedoverlay-arrayindexoutofboundsexception-nullpointerexception-workarounds/
      // populate()がない場合,ユーザ画面からホーム画面に戻って最初の数秒の
      // まだGPS情報が取得されるよりも前の時点でタップすると落ちた。
      populate();
  }

  @Override
  protected OverlayItem createItem(int i) {
    return overlay_items.get(i);
  }

  @Override
  public int size() {
    return overlay_items.size();
  }


  /* --------- アイコンの描画 -------- */


  // アイテムの追加
  public void addOverlayItem(OverlayItem overlayItem) {
    overlay_items.add(overlayItem);
    populate();
  }


  // ダミーアイコンを描画
  public void draw_dummy_icons( GeoPoint g )
  {
    int lat = g.getLatitudeE6();
    int lng = g.getLongitudeE6();

    addOverlayItem( new OverlayItem(
      new GeoPoint(lat + 1000, lng + 1000),
      "ダミー",
      "ダミーですよ!"
    ) );

    addOverlayItem( new OverlayItem(
      new GeoPoint(lat - 3000, lng - 3000),
      "ダミー2",
      "ダミー2ですよ!"
    ) );

  }


  // アイコンをタップ時
  @Override
  protected boolean onTap(int index) {

    OverlayItem item = overlay_items.get(index);

    AlertDialog.Builder dialog = new AlertDialog.Builder(context);
    dialog.setTitle(item.getTitle());
    dialog.setMessage(item.getSnippet());
    dialog.setPositiveButton("はい", new DialogInterface.OnClickListener() {
      public void onClick(DialogInterface dialog, int which) {
        // OKを押した場合の処理

      }
    });
    dialog.setNegativeButton("いいえ", new DialogInterface.OnClickListener() {
      public void onClick(DialogInterface dialog, int which) {
        // NGを押した場合の処理
      }
    });
    dialog.show();

    return true;
  }

}


レイアウトはこんな感じ。

main.xml:

<?xml version="1.0" encoding="UTF-8"?>
<ScrollView
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/scrollView1"
  android:scrollbarAlwaysDrawVerticalTrack="true"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
<LinearLayout
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
>

  <LinearLayout
    android:layout_width="fill_parent"
    android:id="@+id/linearLayout1"
    android:gravity="center_vertical|center_horizontal"
    android:layout_height="335dp">

     <!-- マップ -->
    <com.google.android.maps.MapView
      android:id="@+id/map_view"
      android:clickable="true"
      android:enabled="true"
      android:layout_width="fill_parent"
      android:layout_height="350dp"
      android:apiKey="(デバッグ用のAPIキー)"
    ></com.google.android.maps.MapView>

  </LinearLayout>

</LinearLayout>
</ScrollView>


AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example"
    android:versionCode="1"
    android:versionName="1.0">
  <uses-sdk android:minSdkVersion="3" />

  <application android:icon="@drawable/icon" android:label="@string/app_name">
    <activity android:name=".MapItemTestActivity"
          android:label="@string/app_name">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>
    <uses-library android:name="com.google.android.maps" />
  </application>

  <uses-permission android:name="android.permission.INTERNET" />
  <!-- アプリケーションがGPSへアクセスするのを許可 -->
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
  <!-- アプリケーションがテスト用の位置情報にアクセスするのを許可 -->
  <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" />
</manifest>

これだけでOK。

実行すれば,GPS情報を取得した瞬間に地図が現在地に移動する。

そして,現在地から少し離れた2地点にダミーのアイコンが表示される。

それらのアイコンをタップすると,ダミーのメッセージが表示される。




以下は参考情報:

  • MapViewのHeightが350を超えたあたりから,setBuiltinZoomControlのズーム制御のタップが利かない。なのでHeightを抑え目にしている。
  • マップ上に配置するアイコンは,マップの縮尺に応じて大きさを変更したくなると思うので,リサイズしている。
  • オーバーレイアイテムが持つ「title」と「snippet」が何なのかについては,とりあえず,それぞれ該当アイコンの「名前」と「短い説明」みたいなものだと思えばよい。上記サンプルコード中では,ダイアログ内でgetterを使って取り出している。もしこれ以上の付加的な情報をアイコンに持たせたい場合は,OverlayItemを拡張した独自クラスを使えばよい。
  • 実機でテストする場合,設定>位置情報 内の項目にすべてチェックしておかないと,マップアプリで現在地が取得できないので注意。

マーカー(オーバーレイアイテム)の配置について全般的な参考:

Map上にボタンアイコンを配置する方法について
https://groups.google.com/group/andro...


指定の位置に画像を配置 その2
http://muchag.undo.jp/archives/998

  • 「ItemizedOverlay は raw 型です。総称型 ItemizedOverlay への参照は、パラメーター化する必要があります Java 問題」という警告
    • →クラス定義時にextendsの部分に型パラメータを渡せば警告が消える


Android Maps上の特定の位置に画像を配置する方法
http://www.adamrocker.com/blog/231/an...

  • tapイベントを拾わなくてもよい場合は,単に画像を配置するオーバレイのdrawメソッド内で画像配置すればよい


各クラスにおいて,利用した主なメンバの一覧表:

クラス名主なメソッドイベント他クラスとの関連
MapActivityisRouteDisplayed  
MapViewsetBuiltInZoomControls getOverlays
getController
MapControllersetZoom
animateTo
setCenter
  
MyLocationOverlay runOnFirstFix 
ItemizedOverlayコンストラクタ(アイコン)
createItem
  
OverlayItem   


補足:Drawableな画像のリサイズ方法

//Drawable drawable_icon = this.getResources().getDrawable(R.drawable.icon);

Resources res = this.getResources();
Bitmap bmp_orig = BitmapFactory.decodeResource(res, R.drawable.icon);
Bitmap bmp_resized = Bitmap.createScaledBitmap(bmp_orig, 50, 50, false);
Drawable drawable_icon = new BitmapDrawable(bmp_resized);

サイズ変更後,透過PNGの透過状態も保持された。

Bitmap, Drawableに変換
http://d.hatena.ne.jp/hyoromo/2010061...


Android - Set drawable size programatically
http://stackoverflow.com/questions/46...

  • setBounds()は領域を指定するだけでリサイズではない


Androidで画像がリサイズ可能なクラスを作る
http://d.hatena.ne.jp/hidecheck/20090...

  • Matrixを利用する方法