一応、最後にしたいGoogle Map - Overlayの拡張
今回のPostで今までやってきたGoogle Maps APIを使用した実装の最後としたいと思います。ItemizedOverlayを実装した回のあとがきで少しふれましたが、ItemizedOverlayを使用した場合、Multi ThreadでReal timeにOverlayItemの数などを変更するとAPIの内部でcrashしてしまう場合がありAPIのuserがどうにも対処出来ない(?)問題があります。ということで今回はもう少しcustomizeが可能なOverlayを拡張してみたいと思います
今回やりたいこと
- Overlay拡張classを使用しmap上にiconを表示
- iconをtapするとiconに紐づけられたstringを表示
ちなみに前回やったこと
- MapViewを拡張しZoom level変更event、Center position変更eventを取得(Listenerに通知も)
実装の要点は下記です(前回実装したことに関しては省略)
1. Overlayを拡張したclassを作成
public class MyOverlay extends Overlay { public synchronized void setItems(ArrayList<MyOverlayItem> items){ mItems = items; } @Override public synchronized void draw(Canvas canvas, MapView mapView, boolean shadow) { } @Override public synchronized boolean onTap(GeoPoint p, MapView mapView) { return super.onTap(p, mapView); } }
2. Overlay#draw()をoverrideし保持しているMyOverlayItemのiconを描画する処理を記載
@Override public synchronized void draw(Canvas canvas, MapView mapView, boolean shadow) { //shadowがtrueの場合、Iconの影を描くためのdrawなのでIconのみを描く場合は無視 if(!shadow){ Projection pj = mapView.getProjection(); Point point = new Point(); for(MyOverlayItem item: mItems){ pj.toPixels(item.getGeoPoint(), point); Drawable icon = item.getIcon(); Rect bounds = new Rect(); int halfWidth = icon.getIntrinsicWidth() / 2; bounds.left = point.x - halfWidth; bounds.right = point.x + halfWidth; bounds.top = point.y - icon.getIntrinsicHeight(); bounds.bottom = point.y; icon.setBounds(bounds); icon.draw(canvas); } } }
3. Overlay#onTap()をoverrideしMyOverlayItemをtapした場合はToastを表示する処理を記載
@Override public synchronized boolean onTap(GeoPoint p, MapView mapView) { Projection pj = mapView.getProjection(); int hitIndex = hitTest(pj, p); if(hitIndex != -1){ MyOverlayItem item = mItems.get(hitIndex); Toast.makeText(mContext, item.getString(), Toast.LENGTH_SHORT).show(); } return super.onTap(p, mapView); }
4. Overlay拡張classで表示する(要素を保持する)classを作成
public class MyOverlayItem { }
5. 独自のOverlayを表示するためOverlayを拡張したclassのobjectを取得
mMyOverlay = new MyOverlay(this);
6. MyOverlayItem(表示要素を保持するclass)のobject listを取得しOverlay拡張classにset
ArrayList<MyOverlayItem> items = getMyOverlayItems(); mMyOverlay.setItems(items);
7. overlayのlistにOverlayを拡張したclassを登録
List<Overlay> overlays = mMapView.getOverlays(); overlays.add(mMyOverlay);
気付いた点
- Overlay#onTap()はOverlay#onTouch()が呼ばれた後に呼ばれる
んで実装です
MapActivityなど(Overlay拡張classに関係するcode以外前回と同じ)
package com.android.practice.map; import java.util.ArrayList; import java.util.List; import com.android.practice.map.MyMapView.PanListener; import com.android.practice.map.MyMapView.ZoomListener; 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.MyLocationOverlay; import com.google.android.maps.Overlay; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.location.Location; import android.location.LocationManager; import android.os.Bundle; import android.util.Log; import android.widget.Toast; //MapActivityを拡張したclassを作成 public class MyMapActivity extends MapActivity{ private static final String TAG = "MyMapActivity"; private static final boolean DEBUG = true; private static final String ACTION_LOCATION_UPDATE = "com.android.practice.map.ACTION_LOCATION_UPDATE"; private MyMapView mMapView; private MyLocationOverlay mMyLocationOverlay; private MyItemizedOverlay mItemizedOverlay; private MyOverlay mMyOverlay; private MyMapViewListener mMyMapViewListener; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); initMapSet(); setMapEventListeners(); } @Override protected void onResume() { super.onResume(); setOverlays(); setIntentFilterToReceiver(); requestLocationUpdates(); } @Override protected void onPause() { super.onPause(); resetOverlays(); removeUpdates(); } @Override protected void onDestroy() { super.onDestroy(); } @Override protected boolean isRouteDisplayed() { return false; } private void initMapSet(){ //MyMapView objectの取得 mMapView = (MyMapView)findViewById(R.id.map_view); //MapView#setBuiltInZoomControl()でZoom controlをbuilt-in moduleに任せる mMapView.setBuiltInZoomControls(true); //MapController objectを取得 //mMapController = mMapView.getController(); } //作成したMapView event listener classをlistenerとしてMapView拡張classに登録 private void setMapEventListeners(){ mMyMapViewListener = new MyMapViewListener(); if(mMapView != null){ mMapView.setZoomListener(mMyMapViewListener); mMapView.setPanListener(mMyMapViewListener); } } private void setOverlays(){ //User location表示用のMyLocationOverlay objectを取得 mMyLocationOverlay = new MyLocationOverlay(this, mMapView); //初めてLocation情報を受け取った時の処理を記載 //試しにそのLocationにanimationで移動し、zoom levelを19に変更 mMyLocationOverlay.runOnFirstFix(new Runnable(){ public void run(){ GeoPoint gp = mMyLocationOverlay.getMyLocation(); mMapView.animateTo(gp); mMapView.setZoom(19); } }); //LocationManagerからのLocation update取得 mMyLocationOverlay.enableMyLocation(); //OverlayItemを表示するためのMyItemizedOverlayを拡張したclassのobjectを取得 mItemizedOverlay = new MyItemizedOverlay(getResources().getDrawable(R.drawable.icon), this); //独自のOverlayを表示するためOverlayを拡張したclassのobjectを取得 setMyOverlay(); //overlayのlistにMyLocationOverlayを登録 List<Overlay> overlays = mMapView.getOverlays(); //overlayのlistにOverlayを拡張したclassを登録 overlays.add(mMyOverlay); overlays.add(mMyLocationOverlay); //overlayのlistに拡張したItemizedOverlayを拡張したclassを登録 overlays.add(mItemizedOverlay); } private void setMyOverlay(){ mMyOverlay = new MyOverlay(this); //Overlay拡張classで表示するMyOverlayItemを取得 ArrayList<MyOverlayItem> items = getMyOverlayItems(); //MyOverlayItemをOverlay拡張classにset mMyOverlay.setItems(items); } //今回は適当に取得 private ArrayList<MyOverlayItem> getMyOverlayItems() { ArrayList<MyOverlayItem> items = new ArrayList<MyOverlayItem>(); for(int i = 0; i < 3; i++){ MyOverlayItem item = new MyOverlayItem(); item.setIcon(getResources().getDrawable(R.drawable.pin_map_1 + i)); item.setGeoPoint(new GeoPoint(38994104 + (i * 100), 141317551 + (i * 1000))); item.setString("Pin " + i); items.add(item); } return items; } private void resetOverlays(){ //LocationManagerからのLocation update情報を取得をcancel mMyLocationOverlay.disableMyLocation(); //overlayのlistからMyLocationOverlayを削除 List<Overlay> overlays = mMapView.getOverlays(); overlays.remove(mMyLocationOverlay); //overlayのlistからItemizedOverlayを拡張したclassを削除 overlays.remove(mItemizedOverlay); } private void setIntentFilterToReceiver(){ final IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_LOCATION_UPDATE); registerReceiver(new LocationUpdateReceiver(), filter); } private void requestLocationUpdates(){ final PendingIntent requestLocation = getRequestLocationIntent(this); LocationManager lm = (LocationManager)getSystemService(Context.LOCATION_SERVICE); for(String providerName: lm.getAllProviders()){ if(lm.isProviderEnabled(providerName)){ lm.requestLocationUpdates(providerName, 0, 0, requestLocation); if(DEBUG){ Toast.makeText(this, "Request Location Update", Toast.LENGTH_SHORT).show(); if(DEBUG)Log.d(TAG, "Provider: " + providerName); } } } } private PendingIntent getRequestLocationIntent(Context context){ return PendingIntent.getBroadcast(context, 0, new Intent(ACTION_LOCATION_UPDATE), PendingIntent.FLAG_UPDATE_CURRENT); } private void removeUpdates(){ final PendingIntent requestLocation = getRequestLocationIntent(this); LocationManager lm = (LocationManager)getSystemService(Context.LOCATION_SERVICE); lm.removeUpdates(requestLocation); if(DEBUG)Toast.makeText(this, "Remove update", Toast.LENGTH_SHORT).show(); } private class LocationUpdateReceiver extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if(action == null){ return; } if(action.equals(ACTION_LOCATION_UPDATE)){ //Location情報を取得 Location loc = (Location)intent.getExtras().get(LocationManager.KEY_LOCATION_CHANGED); if(loc != null){ //試しにMapControllerで現在値に移動する mMapView.animateTo(new GeoPoint((int)(loc.getLatitude() * 1E6), (int)(loc.getLongitude() * 1E6))); if(DEBUG)Toast.makeText(context, "latitude:" + loc.getLatitude() + "\nlongitude:" + loc.getLongitude(), Toast.LENGTH_SHORT).show(); } } } } //Zoom level変更/Center position変更event listenerを作成 private class MyMapViewListener implements ZoomListener, PanListener{ @Override public void onZoomChange(MapView mapView, int newZoom, int oldZoom) { Toast.makeText(MyMapActivity.this, "New zoom lv:" + newZoom + "\nOld zoom lv:" + oldZoom, Toast.LENGTH_SHORT).show(); } @Override public void onCenterChange(MapView mapView, GeoPoint newGeoPoint, GeoPoint oldGeoPoint) { Toast.makeText(MyMapActivity.this, "New center latitude:" + newGeoPoint.getLatitudeE6() + "\nNew center longitude:" + newGeoPoint.getLongitudeE6(), Toast.LENGTH_SHORT).show(); } } }
Overlay拡張class
package com.android.practice.map; import java.util.ArrayList; import android.content.Context; import android.graphics.Canvas; import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.util.Log; import android.view.MotionEvent; import android.widget.Toast; import com.google.android.maps.GeoPoint; import com.google.android.maps.MapView; import com.google.android.maps.Overlay; import com.google.android.maps.Projection; //Overlayを拡張したclassを作成 public class MyOverlay extends Overlay { private static final String TAG = "MyOverlay"; private static final boolean DEBUG = true; private static final int NO_HIT = -1; private final Context mContext; private ArrayList<MyOverlayItem> mItems; public MyOverlay(Context context){ mContext = context; mItems = new ArrayList<MyOverlayItem>(); } public synchronized void setItems(ArrayList<MyOverlayItem> items){ mItems = items; } @Override public boolean draw(Canvas canvas, MapView mapView, boolean shadow, long when) { if(DEBUG){ Log.d(TAG, "draw(Canvas canvas, MapView mapView, boolean shadow, long when)"); Log.d(TAG, "shadow: " + shadow); Log.d(TAG, "when: " + when); } return super.draw(canvas, mapView, shadow, when); } //Overlay#draw()をoverrideし保持しているMyOverlayItemのiconを描画する処理を記載 @Override public synchronized void draw(Canvas canvas, MapView mapView, boolean shadow) { if(DEBUG){ Log.d(TAG, "draw(Canvas canvas, MapView mapView, boolean shadow)"); Log.d(TAG, "shadow: " + shadow); } //shadowがtrueの場合、Iconの影を描くためのdrawなのでIconのみを描く場合は無視 if(!shadow){ //Projection objectを取得 Projection pj = mapView.getProjection(); Point point = new Point(); for(MyOverlayItem item: mItems){ pj.toPixels(item.getGeoPoint(), point); Drawable icon = item.getIcon(); Rect bounds = new Rect(); int halfWidth = icon.getIntrinsicWidth() / 2; bounds.left = point.x - halfWidth; bounds.right = point.x + halfWidth; bounds.top = point.y - icon.getIntrinsicHeight(); bounds.bottom = point.y; icon.setBounds(bounds); icon.draw(canvas); } } } @Override public boolean onTouchEvent(MotionEvent e, MapView mapView) { Log.d(TAG, "onTouchEvent"); return super.onTouchEvent(e, mapView); } //Overlay#onTap()をoverrideしMyOverlayItemをtapした場合はToastを表示する処理を記載 @Override public synchronized boolean onTap(GeoPoint p, MapView mapView) { if(DEBUG)Log.d(TAG, "onTap"); Projection pj = mapView.getProjection(); int hitIndex = hitTest(pj, p); if(hitIndex != -1){ MyOverlayItem item = mItems.get(hitIndex); Toast.makeText(mContext, item.getString(), Toast.LENGTH_SHORT).show(); } return super.onTap(p, mapView); } private int hitTest(Projection pj, GeoPoint gp){ int hitIndex = NO_HIT; Point hit = new Point(); pj.toPixels(gp, hit); for(int i = 0; i < mItems.size(); i++){ MyOverlayItem item = mItems.get(i); Point point = new Point(); pj.toPixels(item.getGeoPoint(), point); int halfWidth = item.getIcon().getIntrinsicWidth() / 2; int left = point.x - halfWidth; int right = point.x + halfWidth; int top = point.y - item.getIcon().getIntrinsicHeight(); int bottom = point.y; if(DEBUG){ Log.d(TAG, "left: " + left); Log.d(TAG, "rihgt: " + right); Log.d(TAG, "top: " + top); Log.d(TAG, "bottom: " + bottom); Log.d(TAG, "hit.x: " + hit.x); Log.d(TAG, "hit.y: " + hit.y); } if(left <= hit.x && hit.x <= right){ if(top <= hit.y && hit.y <= bottom){ hitIndex = i; return i; } } } return hitIndex; } }
Overlay拡張classで表示する(要素を保持する)class
package com.android.practice.map; import com.google.android.maps.GeoPoint; import android.graphics.drawable.Drawable; //Overlay拡張classで表示する要素を保持するclassを作成 public class MyOverlayItem { private Drawable mIcon; private GeoPoint mGeoPoint; private String mString; public void setIcon(Drawable icon){ mIcon = icon; } public Drawable getIcon(){ return mIcon; } public void setGeoPoint(GeoPoint geoPoint){ mGeoPoint = geoPoint; } public GeoPoint getGeoPoint(){ return mGeoPoint; } public void setString(String string){ mString = string; } public String getString(){ return mString; } }
と、こんな感じです