Google Maps Android API v2の不具合と対策法


# 何の記事？
Google Maps Android APIでv1からv2に移行する際に詰まった点とその対策法についてです。
Google Maps Android APIはv1からv2で大きく仕様が変わっています。
参考：[Google Maps Android API v2 の v1 からの変更点メモ](http://qiita.com/amay077/items/7ad0244c0fb4b431e090) 

# 不具合って？
1. v2ではマーカーをタップした時にポップアップウィンドウ（InfoWindow）を出す機能が標準装備されました。しかし，titleやsnippetには対応しているものの，他のデータを渡すことが出来ません(setData(obj)みたいなメソッドが用意されていない)。例えば，下記のようなInfoWindowを作成するには，画像のURL(もしくはデータ)を渡す必要がありますが，ミッション・インポッシブル。
![スクリーンショット 2014-01-24 5.32.18 PM.png](https://qiita-image-store.s3.amazonaws.com/0/1706/62a1541e-097c-6376-d8ed-7ab5793e0d61.png "スクリーンショット 2014-01-24 5.32.18 PM.png")

2. InfoWindowに，リモートにある画像を表示しようとすると，別のマーカーをタップしてもそのマーカー用の画像が表示されずに，一つ前のマーカー用の画像が表示されてしまいます。（ビューの生成タイミングの問題だと思います）

3. LatLngのリストを渡して，全てのマーカーが地図内に入るようにズームレベルを調整するケースがよくあると思います。しかし，そのメソッドであるmoveCameraでは，"java.lang.IllegalStateException: Map size should not be 0. Most likely, layout has not yet occured for the map view."というエラーが発生するケースがあります。

# 対処法
## InfoWindowにデータを渡す
Markerを生成すると自動的にMarkerのプロパティとしてIdが付与されます。これを利用して，各Markerに対応したデータをHashMapに渡します。

```java:TemplateMapActivity.java
public class TemplateMapActivity extends FragmentActivity {
	private HashMap<String, String>	hashMap				= new HashMap<String, String>();

public Marker createMarker(LatLng latLng, String title, String snippet) {
		MarkerOptions options = new MarkerOptions();
		options.position(latLng);
		options.title(title);
		options.snippet(snippet);
		Marker marker = map.addMarker(options);
		hashMap.put(marker.getId(), "HogeHoge");
		return marker;
	}
}

```

```java:MyInfoWindowAdapter.java
public class MyInfoWindowAdapter implements InfoWindowAdapter {
	private final View	window;
	private HashMap<String, String>	hashMap;

	public MyInfoWindowAdapter(Context context, HashMap<String, String> hashMap) {
		LayoutInflater inflater = (LayoutInflater) context
						.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
		window = inflater.inflate(R.layout.infowindow, null);
		this.hashMap = hashMap;
	}
@Override
	public View getInfoWindow(Marker marker) {
		render(marker, window);
		return window;
	}

	@Override
	public View getInfoContents(Marker marker) {
		return null;
	}

	private void render(final Marker marker, View view) {
		TextView tv_title = (TextView) view
						.findViewById(R.id.infowindow_title);
		TextView tv_snippet = (TextView) view
						.findViewById(R.id.infowindow_snippet);

		tv_title.setText(marker.getTitle());
		tv_snippet.setText(marker.getSnippet());

		// getting "HogeHoge"
		hashMap.get(marker.getId())
	}
}
```


## InfoWindow内画像データの更新
リモートにある画像をInfowWindowで表示しようとすると画像の取得処理をAsyncTask内に書く必要があり，その間にメインスレッドはgetInfoWindowを終えてしまい，後からViewの更新ができません。そこで，下記のように画像をロードした段階で再描画します。また，無限ループに入るのを防ぐために最後に描画したMarkerを記憶します。（ここでは，リモート画像の取得処理に，[SmartImageView](http://loopj.com/android-smart-image-view/)を使用しています）

```java:MyInfoWindowAdapter.java
public class MyInfoWindowAdapter implements InfoWindowAdapter {
	private final View	window;
	private HashMap<String, String>	hashMap;
	private String					lastMarkerId;

	public MyInfoWindowAdapter(Context context, HashMap<String, String> hashMap) {
		LayoutInflater inflater = (LayoutInflater) context
						.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
		window = inflater.inflate(R.layout.infowindow, null);
		this.hashMap = hashMap;
	}

	@Override
	public View getInfoWindow(Marker marker) {
		render(marker, window);
		return window;
	}

	@Override
	public View getInfoContents(Marker marker) {
		if (marker != null && marker.isInfoWindowShown()) {
			marker.showInfoWindow();
		}
		return null;
	}

	private void render(final Marker marker, View view) {
		if (marker.getId().equals(lastMarkerId))
			return;
		lastMarkerId = marker.getId();

		icon.setImageUrl(
						// get unique URL
						hashMap.get(marker.getId()),
						new OnCompleteListener() {
							@Override
							public void onComplete() {
								getInfoContents(marker);
							}
						});
	}
}
```


## map.moveCameraでエラーが出る
エラーが何故起きるかのよくわかる解説はコチラ[moveCamera(CameraUpdateFactory.newLatLngBounds(… で落ちる](http://qiita.com/amay077/items/070ac1db6b52dd03505f)

上記でもできるが，こんな書き方もできます。

```java:TemplateMapActivity.java
public void setZoom(List<LatLng> latLngList) {
	if (map == null || latLngList.size() == 0)
		return;

	final LatLngBounds.Builder builder = LatLngBounds.builder();
	for (LatLng latLng : latLngList) {
		builder.include(latLng);
	}

	map.setOnCameraChangeListener(new OnCameraChangeListener() {
		@Override
		public void onCameraChange(CameraPosition arg0) {
			map.moveCamera(CameraUpdateFactory.newLatLngBounds(
								builder.build(), 40));
			map.setOnCameraChangeListener(null);
		}
	});
}
```