以下の情報はもう古くなっています。Google Maps Android API v2 で吹き出しへの対応が可能です。
詳しくは、Google Maps Android API v2 などをご覧ください。また、実装方法については、Google Maps Android API v2を使ってみた などをご覧ください。
GoogleMapsを使った際に、地図上にピンなどを配置しますが、そのピンをタップした際に、バルーン(吹き出し)で情報を表示したいことがあります。
なぜか、AndroidのGoogleMapsのライブラリでは、このバルーンを表示するためのAPIがないようです。
いろいろ調べてみると、オープンソースのバルーンのライブラリ(Android MapView Balloons)がありましたので、それを使ってバルーン表示を行う方法をまとめてみました。下記内容は、このライブラリのREADMEをそのまま試しているだけです。
なお、試した環境は以下のとおりです。
OS:Ubuntu 9.04 Desktop 日本語Remix版 (32bit)
Eclipse:3.5 Galileo Java Developer
ADT:9.0.0
1.ライブラリのインストール
ライブラリはgithub、https://github.com/jgilfelt/android-mapviewballoons で公開されています。まず、リポジトリからソースコードを一式落としてきます。
今回は、
git clone git://github.com/jgilfelt/android-mapviewballoons.git
として、gitリポジトリをコピーしました。トップページから、『ダウンロード』のボタンを押せば、tarballまたはzip形式でも落とせます。
次に、Eclipseを立ち上げて、プロジェクトをインポートします。メニューから、『File』→『Import...』→『File System』で落としてきたソースコードのプロジェクトファイルを指定します。
なお、ダウンロードしたファイル一式には、
- ライブラリ本体:android-mapviewballoons
- サンプル:android-mapviewballoons-example
の2つのプロジェクトが含まれています。このサンプルを試しても動作がわかりやすいと思います。
プロジェクトのインポート後、エラーが起っているようなら、『Project』→『Clean』を試してみてください。
なお、手元の環境のせいかもしれませんが、これだけではうまくいかない時もありました。この場合、一度プロジェクトをCloseして再度開いてから、Cleanを実行するとうまくいったりもしました。その他にも、Eclipseを立ち上げ直したりしました。明確な理由が不明なので、トライ&エラーで試したので、ご参考までに。
2.サンプルプロジェクトを作成
適当な名前で、サンプルプロジェクトを作成します。今回は、MapBalloonSampleとしました。
プロジェクト作成時のターゲットのAPIレベルは4以上にします(Android 1.6以上)。また、Google APIs を含むものを指定してください。
プロジェクト作成時に、Activityを生成した場合は、MapActivityの派生クラスに変更してください。
3.ライブラリを使うための設定
プロジェクト作成後、『Properties』→『Android』を選択し、『Library』の『Add...』ボタンを押して、使用するライブラリとして、android-mapviewballoonsを指定します。
指定後は下記のような画面になります。
問題がなければ、OKを押して終了します。
4.サンプルプロジェクトの実装
サンプルプロジェクトは、MapViewだけのレイアウトを持つ、シンプルなものとします。ある一点(東京駅)にアイテムを置き、アイテムをタップするとバルーンを表示し、バルーンをタップすると、緯度経度まで含めてToastでメッセージを表示するようにします。
具体的なソースコードは後半に記載してますので、ここでは実装時の概略だけ説明します。
MapActivityの派生クラス(MainActivityとします)と、ライブラリのBalloonItemizedOverlayの派生クラス(MyBalloonOverlayとします)を作成します。
MainActivity#onCreateでMapViewを取得して、MyBalloonOverlayを生成して、オーバーレイとして追加します。
MyBalloonOverlayは通常のItemizedOverlayの派生クラスと同様に実装します。バルーンのタップ時には、BalloonItemizedOverlayクラスが提供しているonBalloonTapメソッドが呼ばれるので、それをオーバーライドし、バルーンタップ時の処理(Toastの表示)を記述します。
また、今回はMainActivity#onCreateで、OverlayItemを追加しています。
5.実行
エミュレータで動作させてみます。最初に表示後は次のようになります。
アイテム(現在位置を示すのによく使われる、青い点に白い丸枠のある画像)が東京駅に表示されています。このアイテムをタップすると、
このようにバルーンが表示されます。さらにバルーンをタップすると、
Toastでメッセージを表示しました。
6.ソースコード
上記のサンプルプロジェクトで使ったソースコードを示します。文字定数やdrawableは省略しています。
AndridManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.mori_soft.android.mapballoonsample" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".MainActivity" 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"></uses-library> </application> <uses-sdk android:minSdkVersion="4" /> <uses-permission android:name="android.permission.INTERNET"></uses-permission> </manifest>
main.xml(レイアウトファイル)
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <com.google.android.maps.MapView android:id="@+id/mapview" android:layout_width="fill_parent" android:layout_height="fill_parent" android:enabled="true" android:clickable="true" android:apiKey="デバッグ用のAPIキーを設定してください" ></com.google.android.maps.MapView> </LinearLayout>
MainActivity.java
package com.mori_soft.android.mapballoonsample; import java.util.List; 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.Overlay; import com.google.android.maps.OverlayItem; import android.graphics.drawable.Drawable; import android.os.Bundle; public class MainActivity extends MapActivity { private static final int INITIAL_ZOOM_LEVEL = 15; private static final int INITIAL_LATITUDE = 35681634; private static final int INITIAL_LONGITUDE = 139766112; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); MapView map_view = (MapView)findViewById(R.id.mapview); map_view.setBuiltInZoomControls(true); MapController mapController = map_view.getController(); mapController.setZoom(INITIAL_ZOOM_LEVEL); GeoPoint gp = new GeoPoint(INITIAL_LATITUDE, INITIAL_LONGITUDE); mapController.setCenter(gp); // オーバーレイの追加 Drawable d = this.getResources().getDrawable(R.drawable.ic_maps_indicator_current_position); MyBalloonOverlay overlay = new MyBalloonOverlay(d, map_view); OverlayItem oi = new OverlayItem(gp, "サンプル点", "バルーン表示のサンプルデータ"); overlay.addItem(oi); List<Overlay> overlays = map_view.getOverlays(); overlays.add(overlay); } @Override protected boolean isRouteDisplayed() { return false; } }
MyBalloonOverlay.java
package com.mori_soft.android.mapballoonsample; import java.util.ArrayList; import android.content.Context; import android.graphics.drawable.Drawable; import android.widget.Toast; import com.google.android.maps.MapView; import com.google.android.maps.OverlayItem; import com.readystatesoftware.mapviewballoons.BalloonItemizedOverlay; public class MyBalloonOverlay extends BalloonItemizedOverlay<OverlayItem> { private ArrayList<OverlayItem> mItems = new ArrayList<OverlayItem>(); private Context mCtx; public MyBalloonOverlay(Drawable defaultMarker, MapView mapView) { super(boundCenter(defaultMarker), mapView); mCtx = mapView.getContext(); } @Override protected OverlayItem createItem(int i) { return mItems.get(i); } @Override public int size() { return mItems.size(); } public void addItem(OverlayItem oi) { mItems.add(oi); populate(); } @Override protected boolean onBalloonTap(int index, OverlayItem item) { Toast.makeText(mCtx, "バルーンが表示されました\n" + item.getTitle() + " " + item.getSnippet() + "\n" + item.getPoint().getLatitudeE6()/1000000.0 + item.getPoint().getLongitudeE6()/1000000.0, Toast.LENGTH_LONG).show(); return true; } }
6.ライブラリの説明
備忘録代わりに、私なりにこのライブラリについて理解したことをまとめておきます。
このライブラリは、ItemizedOverlayを派生したBalloonItemizedOverlayクラスと、バルーン表示用にFrameLayoutを派生したBallonOverlayViewクラスから構成されています。
BalloonItemizedOverlayでは、アイテムのタップ時に、onTap(int) が呼ばれると、バルーンの描画(に対応する)処理を行います。初回呼び出し時はBalloonOverlayViewが生成されます。生成したBalloonOverlayViewはクラスフィールドに保持しておき、2度目以降の呼び出しでは、Visibilityを切り替えることで、バルーンの表示非表示をコントロールしています。
バルーン表示時には、BalloonOverlayView#setDataメソッドを呼び出すことで、OverlayItemのTitleとSnippetが表示されるようにします。
また、BalloonOverlayViewのLinearLayout領域に対して、setOnTouchListenerで、OnTouchListenerを設定します。このOnTouchListenerはprivateメソッドで生成されており、この内部でACTION_UP時にonBalloonTapを呼ぶようにしています。こうすることで、ライブラリ利用時には、バルーンへのタップに対して、onBalloonTapが呼ばれるようになります。
BallonOverlayViewオブジェクトは、MapViewに対してaddViewされることで、表示可能になります。このさい、OverlayItemの位置を基準にした、レイアウトパラメータが作られることで、アイテムの位置にバルーンが表示可能となります。
なお、onTap(int)メソッドはfinal指定が追加されており、派生クラスでオーバーライドできなくなっています。
BalloonOverlayViewは、バルーンを描画するためのクラスです。リソースに含まれているdrawable(9patch)を表示し、Title, Snippetを表示するためのTextView2つと閉じるためのボタンを持っています。また、バルーンへのタップを判定するために、TextView2つがLinearLayoutに含まれています。
コンストラクタで、レイアウトファイルからViewを生成し、それらをaddViewで追加(BalloonOverlayViewはFrameLayoutの派生クラス)することで動的に生成をおこなっています。
レイアウトファイルでは、一番外側のLinearLayoutのandroid:backgroundに対して、バルーンの9patch(実際にはフォーカスにより画像を切り替えるためのセレクタ)を指定することで、吹き出し風に画像を表示しています。
また、setDataメソッドを持っており、与えられたOverlayItemのTitleとSnippetの内容を適切にTextViewへ設定しています。
バルーンの表示位置をコントロールするために、コンストラクタで、下端からの距離を与えることができるようになっています。
以上