対象読者
Androidアプリケーションの開発を始めたい方で、JavaとEclipseの基本的な知識がある方を対象とします。
地図のイベント処理
地図の移動やマーカーの吹き出しのタップを判断するには、そのイベントを監視する、イベントリスナーと呼ばれる処理を追加します。
地図の位置を変更したときのイベントリスナー
処理手順の1から4まではすでに解説しましたので、今回は、5と6の部分を解説します。
- 地図の中心位置を取得して、Web APIにアクセスする
- 結果を解析する
- マーカーを追加する
- マーカーと駅情報を保存する
- 地図の位置を変更したときのイベントリスナーを追加する
- マーカー(吹き出し)をタップしたときのイベントリスナーを追加する
次のようにActivityには、前回までのコードの修正と追加部分があります。
public class MainActivity extends Activity implements LoaderCallbacks<String> { ~中略~ // 地図の中心位置(1) private CameraPosition centerPosition = null; @Override protected void onCreate(Bundle savedInstanceState) { ~中略~ if (savedInstanceState == null) { ~中略~ } // 地図の中心位置を取得する(2) centerPosition = googleMap.getCameraPosition(); // APIのURLを準備してローダーを初期化する(3) execMoyori(centerPosition); // 地図を移動したときのリスナーを追加する(4) googleMap.setOnCameraChangeListener( // 無名クラス(5) new OnCameraChangeListener() { @Override public void onCameraChange(CameraPosition cameraPosition) { } } ); // 吹き出しのクリックリスナーを追加する(6) googleMap.setOnInfoWindowClickListener(new OnInfoWindowClickListener() { @Override public void onInfoWindowClick(Marker marker) { // 駅情報の取り出し(7) EkiInfo e = ekiMarkerMap.get(marker); Toast ts = Toast.makeText(getBaseContext(), e.name + "(" + e.distance + "m)\n" + "前の駅:" + e.prev + "\n次の駅:" + e.next + "\n" + e.line, Toast.LENGTH_LONG); ts.setGravity(Gravity.TOP, 0, 200); ts.show(); } }); } // APIのURLを準備してローダーを初期化する(前回からパラメータを変更) public void execMoyori(CameraPosition centerPosition) { Bundle bundle = new Bundle(); // 緯度 bundle.putString("y", Double.toString(centerPosition.target.latitude)); // 経度 bundle.putString("x", Double.toString(centerPosition.target.longitude)); bundle.putString("moyori", "http://express.heartrails.com/api/json?method=getStations&"); // LoaderManagerの初期化 getLoaderManager().restartLoader(0, bundle, this); } ~中略~ }
まず、前回のコードからの変更点を説明しておきましょう。
MainActivityクラスに、地図の中心位置を保存するフィールドを追加しています(1)。また、地図の中心位置を取得する処理(2)を、execMoyoriメソッドから分離させました。これは、execMoyoriメソッドの、Web APIのURLを準備してローダーを初期化する処理を、後述するイベントのコールバック処理のなかでも利用したいためです。
次に、イベントリスナーの処理を説明します。
地図の移動の追随は、地図の位置を変更した際に発生するイベントを利用します。このイベントの発生時に何らかの処理を行うには、setOnCameraChangeListenerメソッドを使ってイベントリスナーを登録します(4)。これで、地図の位置が変更された際に、指定したメソッドがコールバックされるようになります。
setOnCameraChangeListenerメソッドの引数は、OnInfoWindowClickListenerインターフェースを実装したクラスです。このインターフェースには、地図の位置を変更した際にコールバックされるonCameraChangeというメソッドのみ定義されています。
このようなイベントリスナーの場合、無名クラスを使って、OnInfoWindowClickListenerインターフェースを実装するようにすると、簡潔に記述できます(5)。
吹き出しに対するイベントリスナーも、同様に追加します(6)。ここでは、OnInfoWindowClickListenerインターフェースを実装する無名クラスを利用しています。
吹き出しをタップすると、onInfoWindowClickメソッドがコールバックされます。そのメソッドの引数のmarkerは、タップされたマーカーとなっていますので、HashMapのekiMarkerMapから、最寄り駅情報を取り出しています(7)。
地図に追随してマーカーを表示する
最後に、地図の位置を変更した際にコールバックされるonCameraChangeメソッド内の処理を追加します。
public class MainActivity extends Activity implements LoaderCallbacks<String> { ~中略~ @Override protected void onCreate(Bundle savedInstanceState) { ~中略~ googleMap.setOnInfoWindowClickListener(new OnInfoWindowClickListener() { @Override public void onCameraChange(CameraPosition cameraPosition) { // 前回から500メートルより大きく移動したら(1) if (0.5 < calcDistance(centerPosition, cameraPosition)) { execMoyori(cameraPosition); centerPosition = cameraPosition; } } }); ~中略~ } // 2点間の距離を求める(km)(2) private double calcDistance(CameraPosition a, CameraPosition b) { double lata = Math.toRadians(a.target.latitude); double lnga = Math.toRadians(a.target.longitude); double latb = Math.toRadians(b.target.latitude); double lngb = Math.toRadians(b.target.longitude); double r = 6378.137; // 赤道半径 return r * Math.acos(Math.sin(lata) * Math.sin(latb) + Math.cos(lata) * Math.cos(latb) * Math.cos(lngb - lnga)); } ~後略~
onCameraChangeメソッドでは、地図の中心位置が、前回保存した位置から500メールより大きくずれた場合に、Web APIを呼び出すようにしています。
onCameraChangeメソッドの引数には、移動した最後の位置が格納されていますので、その位置と、保存しておいた位置(centerPosition)との2点間を比較しています。
2点の緯度、経度から、距離を求めるには、地球の球体を考慮した計算が必要です(2)。計算式については、「2地点間の距離と方位角」というサイトを参考にしました。
これで、地図を移動した場合に、追随してマーカーが表示されるようになります。