views:

251

answers:

1

I want to place an animated marker showing a user's current location in an Overlay over a MapActivity. I need to be able to turn the animation on and off. I thought this could be accomplished as follows:

/**
 * Class to display current location marker above MapActivity
 */
public class MyLocationOverlay extends Overlay {

    private Canvas canvas;
    private final Handler handler = new Handler();
    private Runnable runnable;
    private GeoPoint geoPoint;
    private int xMarker, yMarker;
    private int markerNumber = 0;
    private Bitmap marker, marker0, marker1, marker2, marker3;

    public MyLocationOverlay(Context context) {
        Resources r = context.getResources();
        marker0 = BitmapFactory.decodeResource(r, R.drawable.marker);
        marker1 = BitmapFactory.decodeResource(r, R.drawable.marker1);
        marker2 = BitmapFactory.decodeResource(r, R.drawable.marker2);
        marker3 = BitmapFactory.decodeResource(r, R.drawable.marker3);
        marker = marker0;
    }

    /** Sets location marker is to be drawn at */
    public void setLocation(GeoPoint geoPoint) {
        this.geoPoint = geoPoint;
    }

    /** Sets whether marker is to be animated or static */
    public void animateMarker(boolean on) {
        if (on) {    // activate animation
            if (runnable == null) runnable = new Runnable() {
                public void run() {
                    switch (markerNumber) {
                    case 0: marker = marker0; break;
                    case 1: marker = marker1; break;
                    case 2: marker = marker2; break;
                    case 3: marker = marker3; break;
                    }
                    markerNumber = ++markerNumber % 3;
                    canvas.drawBitmap(marker, xMarker, yMarker, null);
                    handler.postDelayed(this, 500);
                }
            };
        } else {    // cancel animation, make marker static version
            handler.removeCallbacks(runnable);
            runnable = null;
            marker = marker0;
        }
    }

    @Override
    public void draw(Canvas canvas, MapView mapView, boolean shadow) {
        if (shadow == false) {
            Projection projection = mapView.getProjection();

            // convert the location to screen pixels         
            Point point = projection.toPixels(geoPoint, null);

            // record the canvas and the marker position for the animated marker
            this.canvas = canvas;
            xMarker = point.x - marker.getWidth()/2;
            yMarker = point.y - marker.getHeight()/2;

            // draw the marker centred at the location
            canvas.drawBitmap(marker, xMarker, yMarker, null);
        }
        super.draw(canvas, mapView, shadow);
    }

    @Override
    public boolean onTap(GeoPoint point, MapView mapView) { return false; }
}

The constructor is invoked from the main MapActivity.onResume() using

    locationOverlay = MyLocationOverlay(this)

And the animate/draw is invoked by

    GeoPoint point = new GeoPoint(latE6, lonE6);
    locationOverlay.setLocation(point);
    locationOverlay.animateMarker(true);
    mapView.getController().animateTo(point);

However, the run() method never seems to be invoked. What am I doing wrong?

A: 

Ok, to answer my own question – the key lies in the documentation for Runnable.run(): "This method is called when a thread is started that has been created with a class which implements Runnable". Since I'm calling it after the thread in started, I need to kick it off with a call to Handler.postDelayed()

The animateMarker() method now looks like this:

public void animateMarker(boolean on) {
    if (on) {    // activate animation
        if (runnable == null) runnable = new Runnable() {
            public void run() {
                if (canvas!=null) {
                    switch (markerNumber) {
                    case 0: marker = marker0; break;
                    case 1: marker = marker1; break;
                    case 2: marker = marker2; break;
                    case 3: marker = marker3; break;
                    }
                    markerNumber = ++markerNumber % 4;
                    mapView.invalidate(xMarker, yMarker, 
                            xMarker+wMarker, yMarker+hMarker);
                }
                handler.postDelayed(this, 500);
            }
        };
        handler.postDelayed(runnable, 500);
    } else {    // cancel animation, make marker static version
        handler.removeCallbacks(runnable);
        runnable = null;
        marker = marker0;
    }
}

It feels slightly kludgy to record the canvas, mapView & location from the onDraw() method, but it's perhaps the only way...

ChrisV