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...
- 「タップしたら何かのアクションが発生するアイコン」なら ItemizedOverlay を使います
- https://sites.google.com/a/techdoctranslator.com/jp/resources/tutorials/views/hello-mapview のパート 2: オーバーレイアイテムの追加 が参考になる
指定の位置に画像を配置 その2
http://muchag.undo.jp/archives/998
- 「ItemizedOverlay は raw 型です。総称型 ItemizedOverlay への参照は、パラメーター化する必要があります Java 問題」という警告
- →クラス定義時にextendsの部分に型パラメータを渡せば警告が消える
Android Maps上の特定の位置に画像を配置する方法
http://www.adamrocker.com/blog/231/an...
- tapイベントを拾わなくてもよい場合は,単に画像を配置するオーバレイのdrawメソッド内で画像配置すればよい
各クラスにおいて,利用した主なメンバの一覧表:
クラス名 | 主なメソッド | イベント | 他クラスとの関連 |
---|---|---|---|
MapActivity | isRouteDisplayed | ||
MapView | setBuiltInZoomControls | getOverlays getController | |
MapController | setZoom 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を利用する方法