何の記事?
Google Maps Android APIでv1からv2に移行する際に詰まった点とその対策法についてです。
Google Maps Android APIはv1からv2で大きく仕様が変わっています。
参考:Google Maps Android API v2 の v1 からの変更点メモ
不具合って?
-
v2ではマーカーをタップした時にポップアップウィンドウ(InfoWindow)を出す機能が標準装備されました。しかし,titleやsnippetには対応しているものの,他のデータを渡すことが出来ません(setData(obj)みたいなメソッドが用意されていない)。例えば,下記のようなInfoWindowを作成するには,画像のURL(もしくはデータ)を渡す必要がありますが,ミッション・インポッシブル。
-
InfoWindowに,リモートにある画像を表示しようとすると,別のマーカーをタップしてもそのマーカー用の画像が表示されずに,一つ前のマーカー用の画像が表示されてしまいます。(ビューの生成タイミングの問題だと思います)
-
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に渡します。
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;
}
}
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を使用しています)
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(… で落ちる
上記でもできるが,こんな書き方もできます。
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);
}
});
}