Google Map api v2 Android utility libraryをちょっと触ってみた
なんでこのLibraryが作られたの?
Markerピンの処理というと
- Android Tips #26 Google Maps Android API v2 のマーカーをカスタマイズする | Developers.IO
- 各マーカーに対応した処理をする - Androidプログラマへの道 〜 Moonlight 明日香 〜
らへんで出てくる
- GoogleMap::addMarker が同期処理で凄く重い
- Marker数を立てまくるとOOMや応答なしになる事が多いため
で 利点的には
- ClusterManager::addItem 登録を受け付けるだけ
- 順次非同期でMarker作成処理が行われる
- 集合マーカー化することによりMarker数が必然的に減るので軽い
- 集合マーカー条件は、マーカー数でしかできない。*1
導入的な処
dependencies {
compile 'com.google.maps.android:android-maps-utils:0.4.+'
}
ググった場合 v3のjavascriptベースの話がおおくてもんにょりする><
実装ベース的な話
- カスタムClusterManager(extends ClusterManager)
setRenderer(<<カスタムRenderer>>) googleMap.setOnCameraIdleListener(this) //カメラが停止した時 googleMap.setOnMarkerClickListener(this) //マーカーをクリックした時(A) googleMap.setOnInfoWindowClickListener(this)//InfoWindowクリック(B) googleMap.setInfoWindowAdapter(getMarkerManager());//infoWindow自体の関連付け(C) //集合マーカクリック時(Aの処理が分岐) setOnClusterClickListener //単一マーカクリック時(Aの処理が分岐) setOnClusterItemClickListener //集合マーカinfoWindow(Bの処理が分岐) setOnClusterInfoWindowClickListener //単一マーカinfoWindow(Bの処理が分岐) setOnClusterItemInfoWindowClickListener //集合マーカinfoWindow(Cの処理が分岐) getClusterMarkerCollection().setOnInfoWindowAdapter //単一マーカinfoWindow(Cの処理が分岐) getMarkerCollection().setOnInfoWindowAdapter
- カスタムRenderer(extends DefaultClusterRenderer)
の二本立てで実装するのがすっきりするイメージ
mClusterManager.cluster();
で再表示する
ただし、mClusterManager.addItem を1回でもしていないと
mClusterManager.cluster();の実行自体はSkipされる挙動
マーカアイコンのカスタマイズ
のお話
- DefaultClusterRenderer::onBeforeClusterRendered
- 集合マーカ表示直前
- DefaultClusterRenderer::onBeforeClusterItemRendered
- 単一マーカ表示直前
このタイミングで、markerOptionsを設定してやる
super.onBeforeClusterRendered(cluster, ,markerOptions)
を呼ばないと適応されない*2
集合マーカー表示するか否か
- DefaultClusterRenderer::shouldRenderAsCluster
private final static int CLUSTER_SPOT=100 @Override protected boolean shouldRenderAsCluster(Cluster cluster) { return cluster.getSize() > 100; }
- サンプルだと単一マーカの個数で集合マーカー化するみたいな記述になる
- 上記だと100個以上の近接マーカー => 集合マーカ化する
という説明になる。
特定条件で集合マーカー化したくなければ、フラグかなんか持たせて return false を返せば多分いけるはず。
ただこれだと片手落ちで、上記の100個単位にした場合、表示表記も書き換えてやらないと今一*3
標準だと getBucket関数で使われている内部配列に最適化されている*4
書き換えるとしたら
- DefaultClusterRenderer::getColor
- DefaultClusterRenderer::getClusterText
- DefaultClusterRenderer::getBucket(Cluster
cluster) {
あたりか・・
@Override protected int getBucket(Cluster<T> cluster) { int size = cluster.getSize(); int dispSize = CLUSTER_SPOT; if (size <= dispSize { return size; } int cnt = 0; while(size > dispSize){ dispSize += CLUSTER_SPOT; cnt++; } return cnt * CLUSTER_SPOT;//dispSizeよりひとつ減らした値を表示する } protected String getClusterText(int bucket) { if (bucket < CLUSTER_SPOT) { return String.valueOf(bucket); } return String.valueOf(bucket) + "+"; }
最大ズームのときは、Cluster表示解除したい
集合マーカーの距離制御のプロパティはみるからないようなので、
if(map.getCameraPosition().zoom == map.getMaxZoomLevel()){ return false; }
みたいなコードを、shouldRenderAsCluster辺りでかけば、とりあえず動きそうなんだけど
mapに対するアクセスはUIスレッドである必要があるので、ココらへんの判定
OnCameraIdle に突っ込むしか無いのかなーというのが仕方なしな状況
画面内のみ表示する制御
Best way to render only the visible cluster items on a google map (Android) - Codedump.io
の話
ただこれだと意図通りには動かなくて
- DefaultClusterRenderer::onClusterRendered
- 集合マーカ表示時
- DefaultClusterRenderer::onClusterItemRendered
- 単一マーカ表示時
の段階でしか Markerの実体できていないはずなんですけど・・・。
下記の判定自体は使えるんですけどね。
private Boolean isInBounds(LatLng position, LatLngBounds latLngBounds) { return (latLngBounds == null ? mMap.getProjection().getVisibleRegion().latLngBounds : latLngBounds).contains(position); }
で ClusterManagerのコードを読んでると
@Override public void onCameraIdle() { if (mRenderer instanceof GoogleMap.OnCameraIdleListener) { ((GoogleMap.OnCameraIdleListener) mRenderer).onCameraIdle(); } //ClusterManagerの処理 }
と GoogleMap.OnCameraIdleListener を Render側に impliments すれば割り込みが入れられると。このタイミングで判定すれば良い
onCameraIdle は UIThread 上で実行される関数のようです。*5
@Override public void onCameraIdle() { final LatLngBounds latLngBounds = mMap.getProjection().getVisibleRegion().latLngBounds; // 集合マーカ only => 数が少ないので制御要らないかも?? Collection<Marker> clusters = mClusterManager.getClusterMarkerCollection().getMarkers(); for(Marker marker : clusters) { if(isInBounds(marker.getPosition(), latLngBounds)){ marker.setVisible(true); } else{ marker.setVisible(false); } } // 単一マーカー only Collection<Marker> markers = mClusterManager.getMarkerCollection().getMarkers(); for(Marker marker : markers) { if(isInBounds(marker.getPosition(), latLngBounds)){ marker.setVisible(true); } else{ marker.setVisible(false); } } }
でもまあ正直な処、集合マーカーは大した数にはならないので、制御対象外にしてもいいかもしれない。
- onBeforeClusterItemRendered
- => marker.setVisible に変更したのは
- スクロール時の表示を滑らかに見えるようにするため
こうしないと
- MarkerOptionの設定が変更されてしまう*6
- Markerの作り直しだと、markerIdがずれる恐れがある
でもまあ実質的に2万個とかマーカー持てば、それはそれで画面レンダリングが重くなるわけで *7
- 毎回クリアして、画面内だけ作り直す
- ただタイミング的にOnCameraIdleぐらいしか、処理を突っ込めないので
- カメラが止まった時点で、ぱぱっとピン表示されるカクついた動きになってしまう
というのも手かと