42

We're building an application which is using the google maps api for android.

I have my MapController and MapView, and I enable the built-in zoom controls using:

mapView.setBuiltInZoomControls(true);

I would now like to get an event when the user actually zooms on the map, how do I go about that? I can find no such event or any general event where I could detect a change in zoom level.

Update

The mapView.getZoomControls() is deprecated. And the documentation suggests using mapView.setBuiltInZoomControls(bool) instead. This is okay, but I simply cannot figure out how to act on events from the built in zoom controls.

Bjarke Freund-Hansen
  • 28,728
  • 25
  • 92
  • 135
  • 3
    I tried implementing the onUserInteraction event, for the same purpose, but I still have a problem. I'm displaying an image that needs to be scaled according to the zoom level, so obviously I want to call its redraw() method when a user zooms. My problem is that onUserInteraction is invoked when the user clicks on the zoom button, so the zoomLevel isn't updated yet and only on the next interaction (click, span, whatever) the zoomLevel is updated. So, my image is updated only after an interaction after the actual zoom. Any ideas? – Itsik Jun 12 '10 at 23:50
  • 1
    This question and answer are referring to API v1, I added a new answer below for who is using [Google Maps Android API v2](https://developers.google.com/maps/documentation/android/) – tarmelop Feb 23 '13 at 23:10

10 Answers10

41

With the Google Maps Android API v2 you can use a GoogleMap.OnCameraChangeListener like this:

mMap.setOnCameraChangeListener(new OnCameraChangeListener() {

    private float currentZoom = -1;

    @Override
    public void onCameraChange(CameraPosition pos) {
        if (pos.zoom != currentZoom){
            currentZoom = pos.zoom;
            // do you action here
        }
    }
});
tarmelop
  • 936
  • 9
  • 6
25

You can implement your own basic "polling" of the zoom value to detect when the zoom has been changed using the android Handler.

Using the following code for your runnable event. This is where your processing should be done.

private Handler handler = new Handler();

public static final int zoomCheckingDelay = 500; // in ms

private Runnable zoomChecker = new Runnable()
{
    public void run()
    {
        checkMapIcons();

        handler.removeCallbacks(zoomChecker); // remove the old callback
        handler.postDelayed(zoomChecker, zoomCheckingDelay); // register a new one
    }
};

Start a callback event using

protected void onResume()
{
    super.onResume();
    handler.postDelayed(zoomChecker, zoomCheckingDelay);
}

and stop it when you're leaving the activity using

protected void onPause()
{
    super.onPause();
    handler.removeCallbacks(zoomChecker); // stop the map from updating
}

Article this is based on can be found here if you want a longer write up.

Adam Lear
  • 38,111
  • 12
  • 81
  • 101
Kurru
  • 14,180
  • 18
  • 64
  • 84
  • 1
    I've awarded the bounty to this answer, because this is the simplest way of determining whether the map has been moved or zoomed. – Paul Lammertsma Mar 23 '11 at 08:03
7

I use this library which has an onZoom function listener. http://code.google.com/p/mapview-overlay-manager/. Works well.

Abhinav
  • 38,516
  • 9
  • 41
  • 49
5

Here is a clean solution. Simply add this TouchOverlay private class to your activity and a method called onZoom (that is called by this inner class).

Note, you'll have to add this TouchOverlay to your mapView e.g.

mapView.getOverlays().add(new TouchOverlay());

It keeps track of the zoom level whenever the user touches the map e.g. to double-tap or pinch zoom and then fires the onZoom method (with the zoom level) if the zoom level changes.

   private class TouchOverlay extends com.google.android.maps.Overlay {
            int lastZoomLevel = -1;

            @Override
            public boolean onTouchEvent(MotionEvent event, MapView mapview) {
                if (event.getAction() == 1) {
                    if (lastZoomLevel == -1)
                        lastZoomLevel = mapView.getZoomLevel();

                    if (mapView.getZoomLevel() != lastZoomLevel) {
                        onZoom(mapView.getZoomLevel());
                        lastZoomLevel = mapView.getZoomLevel();
                    }
                }
                return false;
            }
        }

        public void onZoom(int level) {
            reloadMapData(); //act on zoom level change event here
        }
Damian
  • 8,062
  • 4
  • 42
  • 43
5

As the OP said, use the onUserInteractionEvent and do the test yourself.

Billy Bob Bain
  • 2,894
  • 18
  • 13
  • 3
    `onUserInteractionEvent()` is called *before* the zoom level changes. This effectively means that with each zoom event, the application monitoring the zoom is running one event behind. – Paul Lammertsma Mar 16 '11 at 11:41
  • @Paul: Hi. Originally I did not consider this, but it seems you are right, the toast I display are always one even behind. – Bjarke Freund-Hansen Mar 18 '11 at 10:35
  • 1
    You can create a `Handler` instance and periodically check for zoom level changes through something like `postDelayed(new Runnable() {...}, 500)`, but I really don't like that solution. – Paul Lammertsma Mar 18 '11 at 16:45
4

The android API suggests you use ZoomControls which has a setOnZoomInClickListener()

To add these zoom controls you would:

ZoomControls mZoom = (ZoomControls) mapView.getZoomControls();

And then add mZoom to your layout.

JasonOfEarth
  • 739
  • 7
  • 10
  • Thanks for the answer. The MapView.getZoomControls() is deprecated, with the suggestion of using the MapView.setBuiltInZoomControls(boolean) method. But I cannot find anywhere to get any events from these built-in zoom controls. Ref: http://code.google.com/android/add-ons/google-apis/reference/com/google/android/maps/MapView.html#getZoomControls(). – Bjarke Freund-Hansen Jan 06 '10 at 14:58
  • I see. Well it looks like there is an undocumented mapView.getZoomButtonsController() that you could use, described in http://code.google.com/p/android/issues/detail?id=3348 otherwise you'll just have to use the mapview.onkeydown and test for the zoom. – JasonOfEarth Jan 06 '10 at 15:20
  • I got what I wanted using the onUserInteraction event, and manually testing for a change in the zoom level. It works really nice with a toast message notifying the user of the necessity of zooming-in to view my overlay. – Bjarke Freund-Hansen Jan 06 '10 at 17:37
4

You Can use

ZoomButtonsController zoomButton = mapView.getZoomButtonsController();
zoomButton.setOnZoomListener(listener);

hope it helps

Marko
  • 20,385
  • 13
  • 48
  • 64
  • 3
    This very specifically answers the OP's question, but only raises an event for zoom events through the zoom buttons. Pinching, for instance, does not call the listener's callback. – Paul Lammertsma Mar 23 '11 at 08:06
3

I think there might another answer to this question. The Overlay class draw method is called after any zoom and I believe this is called after the zoom level changes. Could you not architect your app to take advantage of this? You could even use a dummy overlay just to detect this if you wanted to. Would this not be more efficient than using a runnable with a delay?

@Override
public boolean draw (Canvas canvas, MapView mapView, boolean shadow, long when) {
    int zoomLevel = mapView.getZoomLevel();
    // do what you want with the zoom level
    return super.draw(canvas, mapView, shadow, when);
}
Bjarke Freund-Hansen
  • 28,728
  • 25
  • 92
  • 135
Paul
  • 196
  • 1
  • 10
2

For pre-V2.0, I made a class which extends MapView that alerts a listener with events when the map region starts changing (onRegionBeginChange) and stops changing (onRegionEndChange).

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);
    }
}
David
  • 834
  • 1
  • 10
  • 27
1

I used a mixture of the above. I found that using the timmer to start a thread every half a second cause the map to be really jenky. Probably because I was using a couple of If statments everytime. So I started the thread on a post delay of 500ms from the onUserInteraction. This gives enough time for the zoomLevel to update before the thread starts to run thus getting the correct zoomlevel without running a thread every 500ms.

public void onUserInteraction() {
    handler.postDelayed(zoomChecker, zoomCheckingDelay);
}
jiduvah
  • 5,108
  • 6
  • 39
  • 55