How to implemennt OnZoomListener on MapView - android

I would like to have onZoomListener on my MapView.
The code below is what I have done. It registers if zoom buttons are tapped. Since all new phones now supports pinch to zoom, this is useless. Does anybody have idea how to do real onZoomListener? Thanks.
OnZoomListener listener = new OnZoomListener() {
#Override
public void onVisibilityChanged(boolean arg0) {
// TODO Auto-generated method stub
}
#Override
public void onZoom(boolean arg0) {
Log.d(TAG, "ZOOM CHANGED");
// TODO Auto-generated method stub
}
};
ZoomButtonsController zoomButton = mapView.getZoomButtonsController();
zoomButton.setOnZoomListener(listener);

I had to subclass MapView and override dispatchDraw
Here is the code:
int oldZoomLevel=-1;
#Override
public void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (getZoomLevel() != oldZoomLevel) {
Log.d(TAG, "ZOOOMED");
oldZoomLevel = getZoomLevel();
}
}
This blog helped me a lot: http://pa.rezendi.com/2010/03/responding-to-zooms-and-pans-in.html
Above works great. Is there maybe simpler solution?
I tried to implement onTouchListener on MapView directly but touch event would be detected only once if onTouchListener would return false. If it would return true, touch would be detected every time, but map zooming and panning wouldn't work.

I put all the code from above together and came up with this class:
package at.blockhaus.wheels.map;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapView;
public class CustomMapView extends MapView {
private int oldZoomLevel = -1;
private GeoPoint oldCenterGeoPoint;
private OnPanAndZoomListener mListener;
public CustomMapView(Context context, String apiKey) {
super(context, apiKey);
}
public CustomMapView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomMapView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setOnPanListener(OnPanAndZoomListener listener) {
mListener = listener;
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_UP) {
GeoPoint centerGeoPoint = this.getMapCenter();
if (oldCenterGeoPoint == null ||
(oldCenterGeoPoint.getLatitudeE6() != centerGeoPoint.getLatitudeE6()) ||
(oldCenterGeoPoint.getLongitudeE6() != centerGeoPoint.getLongitudeE6()) ) {
mListener.onPan();
}
oldCenterGeoPoint = this.getMapCenter();
}
return super.onTouchEvent(ev);
}
#Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (getZoomLevel() != oldZoomLevel) {
mListener.onZoom();
oldZoomLevel = getZoomLevel();
}
}
}
And the corresponding listener interface:
package at.blockhaus.wheels.map;
public interface OnPanAndZoomListener {
public void onPan();
public void onZoom();
}

Maybe a bit late, but have you tried with an OnOverlayGestureListener?
http://code.google.com/p/mapview-overlay-manager/wiki/OnOverlayGestureListener

Try via this method
map.setMapListener(new MapListener()
{
....
#Override
public boolean onZoom(ZoomEvent event)
{
return false;
}
});

Edit: I believe what you've found is probably the only mechanism to allow this detection. (Editing my post to remove misinformation) Extending the MapView and overriding the onDraw() method would work. One consideration to make will be how often to fire the code listening to zoom. For instance, while zooming the onDraw may be called dozens of times each with a different zoom level. This may cause poor performance if your listener is firing every time. You could throttle this by determining that a zoom change has taken place and then waiting for the zoom level to be the same on two subsequent redraws before firing the event. This all depends on how you want your code to be called.

use dispatchTouch() method to detect touch events on the map and make sure you call the super() method to carry out the default map touch functions. (Setting onTouchListener will disable the in built events like zooming and panning if you return true from your functiion, and will handle touch only once if you return false.)
in dispatchTouch method, you can check for no of touch points by using event.getPointerCount(). use Action_UP to detect whether the user has zoomed out or not by calling mapview.getZoomLevel(); if you find the zoom level changed, you can carry out your actions.

i implemented it the following way, and it works for single/multitouch :
#Override
protected void dispatchDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.dispatchDraw(canvas);
if (oldZoomLevel == -1) {
oldZoomLevel = getZoomLevel();
}
if (oldZoomLevel < getZoomLevel()) {
System.out.println("zoomed in");
if (mOnZoomInListener != null) {
mOnZoomInListener
.onZoomedIn(this, oldZoomLevel, getZoomLevel());
}
}
if (oldZoomLevel > getZoomLevel()) {
System.out.println("zoomed out");
if (mOnZoomOutListener != null) {
mOnZoomOutListener.onZoomedOut(this, oldZoomLevel,
getZoomLevel());
}
}
It worked for me!

MapView provides build-in zoom controls that support pinch.
If it doesn't work out of the box you could try setting mapView.setBuiltInZoomControls(true);
This way you should not need the OnZoomListener.

Related

How to get when an ImageView is completely loaded in Android

I'm developing an App which draws lines over a bunch of Images. To choose these images, I have a radio group and, whenever the user clicks in a radio button, the image is load with all its own drawings.
In my radio listenner I have the following code:
bitmap = BitmapUtils.decodeSampledBitmapFromResource(root + DefinesAndroid.CAMINHO_SHOPPINGS_SDCARD + nomeImagemAtual, size.x, size.y);
mImage.setImageBitmap(bitmap);
mImage.setDrawLines(true);
mImage.setImageBitmap(loadBitmapFromView(mImage));
the decodeSampledBitmapFromResource method I got from this link on android developers (it loads bitmaps more effitiently) http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
And here's the method I call to get a Bitmap of a View
public static Bitmap loadBitmapFromView(View v) {
Bitmap b = Bitmap.createBitmap( v.getLayoutParams().width, v.getLayoutParams().height, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
v.layout(0, 0, v.getLayoutParams().width, v.getLayoutParams().height);
v.draw(c);
return b;
}
I'm setting the image Bitmap of mImage because I'm using ImageViewTouch library (which enables pinch zooming over an ImageView) and if I don't do it, all the canvas drawing is deleted with any interaction over the image (like zooming in/out).
The error log is the following
07-11 21:13:41.567: E/AndroidRuntime(20056): java.lang.IllegalArgumentException: width and height must be > 0
07-11 21:13:41.567: E/AndroidRuntime(20056): at android.graphics.Bitmap.createBitmap(Bitmap.java:638)
07-11 21:13:41.567: E/AndroidRuntime(20056): at android.graphics.Bitmap.createBitmap(Bitmap.java:620)
I'm almost sure that this error is occuring cause the image bitmap is not completely loaded when I call getBitmapFromView method.
How can I know when the view is loaded completely?
Call loadBitmapFromView in a such way:
mImage.post(new Runnable() {
#Override
public void run() {
loadBitmapFromView(mImage);
}
});
Runnable provided to post() method will be executed after view measuring and layouting, so getWidth() and getHeight() will return actual width and height.
What else can you do, is measuring View manually, by invoking measure, and then taking result from getMeasuredWidth() and getMeasuredHeight(). But I do not recommend this way.
There is actually another, more reliable way to do that by using ViewTreeObserver.OnPreDrawListener. And here is an example:
mImage.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
try {
loadBitmapFromView(mImage);
// Note that returning "true" is important,
// since you don't want the drawing pass to be canceled
return true;
} finally {
// Remove listener as further notifications are not needed
mImage.getViewTreeObserver().removeOnPreDrawListener(this);
}
}
});
Using OnPreDrawListener guarantees that View was measured and layouted, while View#post(Runnable) just executes your Runnable when all Views are already most likely measured and layouted.
Below is a working NotifyImageView class that incorporates Dmitry's method above
and adds code to notify only after the ImageView is truly usable.
I hope you find it useful.
`
public interface NotifyImageHolder {
public void notifyImageChanged(final NotifyImageView thePosterImage, final int width, final int height);
}
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
public class NotifyImageView extends ImageView {
private boolean mImageChanged;
private NotifyImageHolder mHolder;
private boolean mImageFinished;
public NotifyImageView(Context context) {
super(context);
init();
}
public NotifyImageView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public NotifyImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
protected void init() {
mImageChanged = false;
mImageFinished = false;
mHolder = null;
monitorPreDraw();
}
// so we can tell when the image finishes loading..
protected void monitorPreDraw() {
final NotifyImageView thePosterImage = this;
getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
try {
return true; //note, that "true" is important, since you don't want drawing pass to be canceled
} finally {
getViewTreeObserver().removeOnPreDrawListener(this); // we don't need any further notifications
thePosterImage.buildDrawingCache();
mImageFinished = true;
}
}
});
}
public void setNotifyImageHolder(NotifyImageHolder holder) {
this.mHolder = holder;
}
public boolean isImageChanged() {
return mImageChanged;
}
public boolean isImageFinished() {
return mImageFinished;
}
public void notifyOff() {
mHolder = null;
}
// the change notify happens here..
#Override
public void setImageDrawable(Drawable noPosterImage) {
super.setImageDrawable(noPosterImage);
if (mHolder != null && mImageFinished) {
mImageFinished = false; // we send a single change-notification only
final NotifyImageView theNotifyImageView = this;
theNotifyImageView.post(new Runnable() {
#Override
public void run() {
if (mHolder != null) {
int width = getMeasuredWidth();
int height = getMeasuredHeight();
mImageChanged = true;
mHolder.notifyImageChanged(theNotifyImageView, width, height);
}
}
});
}
}
}
`

Android Map Marker on Longpress is Plotted Below the Area That is Actually LongPressed (on API 15)

I have been racking my brains for days now on this and have Googled and SO'ed but can't seem to find anyone else reporting such problem(could have missed it).
I have a customized Mapview Class that listens for longpress among others and am using it to plot markers on my map. It plots okay on API-8. But on API-15 the marker is offsetted by about 2cm below where the user finger is doing the longpress. This is observed for both actual device (samsung s2) and eclipse emulator. Also the finger area long-pressed versus the marker area plotted(offset by about 2cm) is observed on all zoom levels.
Here is my customized Mapview Class (yanked it from somewhere):
public class MyCustomMapView extends MapView {
public interface OnLongpressListener {
public void onLongpress(MapView view, GeoPoint longpressLocation);
}
static final int LONGPRESS_THRESHOLD = 500;
private GeoPoint lastMapCenter;
private Timer longpressTimer = new Timer();
private MyCustomMapView.OnLongpressListener longpressListener;
public MyCustomMapView(Context context, String apiKey) {
super(context, apiKey);
}
public MyCustomMapView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyCustomMapView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setOnLongpressListener(MyCustomMapView.OnLongpressListener listener) {
longpressListener = listener;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
handleLongpress(event);
return super.onTouchEvent(event);
}
private void handleLongpress(final MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
// Finger has touched screen.
longpressTimer = new Timer();
longpressTimer.schedule(new TimerTask() {
#Override
public void run() {
GeoPoint longpressLocation = getProjection().fromPixels((int)event.getX(), (int)event.getY());
/*
* Fire the listener. We pass the map location
* of the longpress as well, in case it is needed
* by the caller.
*/
longpressListener.onLongpress(MyCustomMapView.this, longpressLocation);
}
}, LONGPRESS_THRESHOLD);
lastMapCenter = getMapCenter();
}
if (event.getAction() == MotionEvent.ACTION_MOVE) {
if (!getMapCenter().equals(lastMapCenter)) {
// User is panning the map, this is no longpress
longpressTimer.cancel();
}
lastMapCenter = getMapCenter();
}
if (event.getAction() == MotionEvent.ACTION_UP) {
// User has removed finger from map.
longpressTimer.cancel();
}
if (event.getPointerCount() > 1) {
// This is a multitouch event, probably zooming.
longpressTimer.cancel();
}
}
And this is how I call the Class above:
custom_marker = getResources().getDrawable(R.drawable.marker3);
custom_marker.setBounds(-custom_marker.getIntrinsicWidth(), -custom_marker.getIntrinsicHeight(), 0, 0);
customSitesOverlay = new CustomSitesOverlay(custom_marker);
mapView.getOverlays().add(customSitesOverlay);
customSitesOverlay.addOverlay(new OverlayItem(longpressLocation, "User Marker", id));
It looks like a setBounds problem. You are setting the marker bounds to center in the bottom-right corner of drawable. Is that the intented behaviour?
You can set the marker to center in the bottom-center (which is the most usual type or markers) setting the bounds to:
custom_marker.setBounds(-custom_marker.getIntrinsicWidth()/2, -custom_marker.getIntrinsicHeight(), custom_marker.getIntrinsicWidth()/2, 0);
good luck.

How to capture the end of a Zoom In/Out animation from a MapController?

Just an interesting query here, is there a way to capture when a zoom animation sequence has ended when calling either:
MapController.zoomIn() or MapController.zoomOut();
I know that it does kick off an animation sequence to zoom in/out to the next level, however there is no known way I can find/google search, etc to find out when it finishes that sequence. I need to be able to run an update command when that is stopped so my map updates correctly.
I've found that by running the update command after calling the above function the Projection isn't from the zoom out level but somewhere inbetween (so I can't show all the data I need).
I have to admit I punted here, it's a hack but it works great. I started off with a need to know when a zoom occured, and once I hooked into that (and after some interesting debugging) I found some values were "between zoom" values, so I needed to wait till after the zoom was done.
As suggested elsewhere on Stack Overflow my zoom listener is an overridden MapView.dispatchDraw that checks to see if the zoom level has changed since last time.
Beyond that I added an isResizing method that checks if the timestamp is more than 100ms since the getLongitudeSpan value stopped changing. Works great. Here is the code:
My very first Stack Overflow post! Whoo Hoo!
public class MapViewWithZoomListener extends MapView {
private int oldZoomLevel = -1;
private List<OnClickListener> listeners = new ArrayList<OnClickListener>();
private long resizingLongitudeSpan = getLongitudeSpan();
private long resizingTime = new Date().getTime();
public MapViewWithZoomListener(Context context, String s) {
super(context, s);
}
public MapViewWithZoomListener(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
}
public MapViewWithZoomListener(Context context, AttributeSet attributeSet, int i) {
super(context, attributeSet, i);
}
public boolean isResizing() {
// done resizing if 100ms has elapsed without a change in getLongitudeSpan
return (new Date().getTime() - resizingTime < 100);
}
public void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (getZoomLevel() != oldZoomLevel) {
new AsyncTask() {
#Override
protected Object doInBackground(Object... objects) {
try {
if (getLongitudeSpan() != resizingLongitudeSpan) {
resizingLongitudeSpan = getLongitudeSpan();
resizingTime = new Date().getTime();
}
Thread.sleep(125); //slightly larger than isMoving threshold
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Object o) {
super.onPostExecute(o);
if (!isResizing() && oldZoomLevel != getZoomLevel()) {
oldZoomLevel = getZoomLevel();
invalidate();
for (OnClickListener listener : listeners) {
listener.onClick(null);
}
}
}
}.execute();
}
}
public void addZoomListener(OnClickListener listener) {
listeners.add(listener);
}

How to catch that map panning and zoom are really finished?

I am trying to write an application that will dynamically load data to map while user pans or zooms it.
I need to track when the map is finished to change its view state (stops panning or zooming) and then load a new portion of data for creating markers. But in fact Google Maps API doesn't have any events to handle this.
There are some methods like creating an empty overlay to control onTouch events and so on, but map panning could last long after user finished his touch because GMaps use some kind of inertia to make the pan smoother.
I tried to subclass MapView but its draw() method is final thus it cannot be overridden.
Any ideas how to make precise handling of pan and zoom finishing?
Hours of researching and some decision was found. It has some cons and pros which I'll describe further.
The main thing we should do is to override some MapView's methods to handle its drawing behavior. In case we cannot override draw() method we should find another way in. There is another one derivative from View which may be overridden - computeScroll() method. It is called repeatedly as map continues padding. All we have to do is to set some time threshold to catch if computeScroll is not called anymore this time.
Here is what I did:
import java.util.Timer;
import java.util.TimerTask;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapView;
public class EnhancedMapView extends MapView {
public interface OnZoomChangeListener {
public void onZoomChange(MapView view, int newZoom, int oldZoom);
}
public interface OnPanChangeListener {
public void onPanChange(MapView view, GeoPoint newCenter, GeoPoint oldCenter);
}
private EnhancedMapView _this;
// Set this variable to your preferred timeout
private long events_timeout = 500L;
private boolean is_touched = false;
private GeoPoint last_center_pos;
private int last_zoom;
private Timer zoom_event_delay_timer = new Timer();
private Timer pan_event_delay_timer = new Timer();
private EnhancedMapView.OnZoomChangeListener zoom_change_listener;
private EnhancedMapView.OnPanChangeListener pan_change_listener;
public EnhancedMapView(Context context, String apiKey) {
super(context, apiKey);
_this = this;
last_center_pos = this.getMapCenter();
last_zoom = this.getZoomLevel();
}
public EnhancedMapView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public EnhancedMapView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setOnZoomChangeListener(EnhancedMapView.OnZoomChangeListener l) {
zoom_change_listener = l;
}
public void setOnPanChangeListener(EnhancedMapView.OnPanChangeListener l) {
pan_change_listener = l;
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
if (ev.getAction() == 1) {
is_touched = false;
} else {
is_touched = true;
}
return super.onTouchEvent(ev);
}
#Override
public void computeScroll() {
super.computeScroll();
if (getZoomLevel() != last_zoom) {
// if computeScroll called before timer counts down we should drop it and start it over again
zoom_event_delay_timer.cancel();
zoom_event_delay_timer = new Timer();
zoom_event_delay_timer.schedule(new TimerTask() {
#Override
public void run() {
zoom_change_listener.onZoomChange(_this, getZoomLevel(), last_zoom);
last_zoom = getZoomLevel();
}
}, events_timeout);
}
// Send event only when map's center has changed and user stopped touching the screen
if (!last_center_pos.equals(getMapCenter()) || !is_touched) {
pan_event_delay_timer.cancel();
pan_event_delay_timer = new Timer();
pan_event_delay_timer.schedule(new TimerTask() {
#Override
public void run() {
pan_change_listener.onPanChange(_this, getMapCenter(), last_center_pos);
last_center_pos = getMapCenter();
}
}, events_timeout);
}
}
}
Then you should register event handlers in your MapActivity like this:
public class YourMapActivity extends MapActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mv = new EnhancedMapView(this, "<your Maps API key here>");
mv.setClickable(true);
mv.setBuiltInZoomControls(true);
mv.setOnZoomChangeListener(new EnhancedMapView.OnZoomChangeListener() {
#Override
public void onZoomChange(MapView view, int newZoom, int oldZoom) {
Log.d("test", "zoom changed from " + oldZoom + " to " + newZoom);
}
}
mv.setOnPanChangeListener(new EnhancedMapView.OnPanChangeListener() {
public void onPanChange(MapView view, GeoPoint newCenter, GeoPoint oldCenter) {
Log.d("test", "center changed from " + oldCenter.getLatitudeE6() + "," + oldCenter.getLongitudeE6() + " to " + newCenter.getLatitudeE6() + "," + newCenter.getLongitudeE6());
}
}
}
Now what about advantages and disadvantages of this approach?
Advantages:
- Events handles either way map was panned or zoomed. Touch event, hardware keys used, even programmatically fired events are handled (like setZoom() or animate() methods).
- Ability to skip unnecessary loading of data if user clicks zoom button several times quickly. Event will fire only after clicks will stop.
Disadvantages:
- It is quite not possible to cancel zooming or panning action (maybe I'll add this ability in the future)
Hope this little class will help you.
The MapChange project, originally posted on a similar question here, helped me to fullfil the same task you asked for.
Now this can be used:
googleMap.setOnCameraIdleListener {
//Do your thing
}
Callback interface for when camera movement has ended

How can I detect if an Android MapView has been panned or zoomed?

I'm creating an Android app that searches for items based on the visible area of the MapView. Is there a way to set up a listener on my MapView to detect when a map has been panned or zoomed?
try mapview-overlay-manager, it is a extension for overlayer for android maps,
it has some simplified OnGestureListener, few example:
onSingleTap(MotionEvent, ManagedOverlay, GeoPoint, OverlayItem)
onDoubleTap(MotionEvent, ManagedOverlay, GeoPoint, OverlayItem)
onLongPress(MotionEvent, ManagedOverlay, GeoPoint, OverlayItem)
onZoom(ZoomEvent, ManagedOverlay)
onScrolled(...)
link:http://code.google.com/p/mapview-overlay-manager/ hope it helps
You can create a SimpleMapView that extends MapView.
public class SimpleMapView extends MapView {
private int currentZoomLevel = -1;
private GeoPoint currentCenter;
private List<ZoomChangeListener> zoomEvents = new ArrayList<ZoomChangeListener>();
private List<PanChangeListener> panEvents = new ArrayList<PanChangeListener>();
public SimpleMapView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public SimpleMapView(Context context, String apiKey) {
super(context, apiKey);
}
public SimpleMapView(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
*
* #return
*/
public int[][] getBounds() {
GeoPoint center = getMapCenter();
int latitudeSpan = getLatitudeSpan();
int longtitudeSpan = getLongitudeSpan();
int[][] bounds = new int[2][2];
bounds[0][0] = center.getLatitudeE6() - (latitudeSpan / 2);
bounds[0][1] = center.getLongitudeE6() - (longtitudeSpan / 2);
bounds[1][0] = center.getLatitudeE6() + (latitudeSpan / 2);
bounds[1][1] = center.getLongitudeE6() + (longtitudeSpan / 2);
return bounds;
}
public boolean onTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_UP) {
GeoPoint centerGeoPoint = this.getMapCenter();
if (currentCenter == null ||
(currentCenter.getLatitudeE6() != centerGeoPoint.getLatitudeE6()) ||
(currentCenter.getLongitudeE6() != centerGeoPoint.getLongitudeE6()) ) {
firePanEvent(currentCenter, this.getMapCenter());
}
currentCenter = this.getMapCenter();
}
return super.onTouchEvent(ev);
}
#Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if(getZoomLevel() != currentZoomLevel){
fireZoomLevel(currentZoomLevel, getZoomLevel());
currentZoomLevel = getZoomLevel();
}
}
#Override
public void setSatellite(boolean on){
super.setSatellite(on);
}
#Override
public MapController getController(){
return super.getController();
}
private void fireZoomLevel(int old, int current){
for(ZoomChangeListener event : zoomEvents){
event.onZoom(old, current);
}
}
private void firePanEvent(GeoPoint old, GeoPoint current){
for(PanChangeListener event : panEvents){
event.onPan(old, current);
}
}
public void addZoomChangeListener(ZoomChangeListener listener){
this.zoomEvents.add(listener);
}
public void addPanChangeListener(PanChangeListener listener){
this.panEvents.add(listener);
}
}
You have the Listeners you can put the code for pan or zoom.
Then in your xml:
<com.androidnatic.maps.SimpleMapView android:clickable="true"
android:layout_height="match_parent" android:id="#+id/mapView"
android:layout_width="match_parent"
android:apiKey="xxx">
</com.androidnatic.maps.SimpleMapView>
And then in your code you can specify the Pan Listener:
mapView.addPanChangeListener(new PanChangeListener() {
#Override
public void onPan(GeoPoint old, GeoPoint current) {
//TODO
}
});
Sadly, there is no built-in functionality to do this in the MapView tools (a strange oversight since this functionality is in the JavaScript SDK, as well as the iOS SDK).
You can deal with this easily enough though by using a Runnable and just polling the MapView. I do this by keeping track of the "last" state of:
getLatitudeSpan();
getLongitudeSpan();
getCenter();
getZoomLevel();
And then comparing them to the current values. If the values of changed, you know the map view has moved. If not, you can do nothing.
Either way, reschedule the runnable for another run after 500ms or so and repeat the process. You can use onResume() and onPause() to remove the callback for the Runnable and restart it as necessary.
The MapView class can track changes using the onLayout method.
i.e.
class CustomMapView extends MapView {
protected void onLayout(boolean changed,int left, int right, int top, int bottom){
super.onLayout(changed,left, right, top, bottom);
if(changed){
// do something special
}
}
}
The only way that I can think of is to extends the MapView and override the OnTouchEvent and watch for the Up action. This will tell you that the user has finished moving and you can get the lat/lon span to determine the region you should check out.
Old question, but I wrote a class a while back called ExMapView which fires an event when the map region starts changing (onRegionBeginChange) and when it stops changing (onRegionEndChange). This class is for use with the old MapView, not V2.0. Hope it helps someone.
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapView;
public class ExMapView extends MapView {
private static final String TAG = ExMapView.class.getSimpleName();
private static final int DURATION_DEFAULT = 700;
private OnRegionChangedListener onRegionChangedListener;
private GeoPoint previousMapCenter;
private int previousZoomLevel;
private int changeDuration; // This is the duration between when the user stops moving the map around and when the onRegionEndChange event fires.
private boolean isTouched = false;
private boolean regionChanging = false;
private Runnable onRegionEndChangeTask = new Runnable() {
public void run() {
regionChanging = false;
previousMapCenter = getMapCenter();
previousZoomLevel = getZoomLevel();
if (onRegionChangedListener != null) {
onRegionChangedListener.onRegionEndChange(ExMapView.this, previousMapCenter, previousZoomLevel);
}
}
};
public ExMapView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ExMapView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public ExMapView(Context context, String apiKey) {
super(context, apiKey);
init();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
isTouched = event.getAction() != MotionEvent.ACTION_UP;
return super.onTouchEvent(event);
}
#Override
public void computeScroll() {
super.computeScroll();
// If the map region is still changing (user is still scrolling or zooming), reset timer for onRegionEndChange.
if ((!isTouched && !getMapCenter().equals(previousMapCenter)) || (previousZoomLevel != getZoomLevel())) {
// If the region has just begun changing, fire off onRegionBeginChange event.
if (!regionChanging) {
regionChanging = true;
if (onRegionChangedListener != null) {
onRegionChangedListener.onRegionBeginChange(this, previousMapCenter, previousZoomLevel);
}
}
// Reset timer for onRegionEndChange.
removeCallbacks(onRegionEndChangeTask);
postDelayed(onRegionEndChangeTask, changeDuration);
}
}
private void init() {
changeDuration = DURATION_DEFAULT;
previousMapCenter = getMapCenter();
previousZoomLevel = getZoomLevel();
}
public void setOnRegionChangedListener(OnRegionChangedListener listener) {
onRegionChangedListener = listener;
}
public void setChangeDuration(int duration) {
changeDuration = duration;
}
public interface OnRegionChangedListener {
public abstract void onRegionBeginChange(ExMapView exMapView, GeoPoint geoPoint, int zoomLevel);
public abstract void onRegionEndChange(ExMapView exMapView, GeoPoint geoPoint, int zoomLevel);
}
}

Categories

Resources