Search code examples
androidandroid-mapviewgoogle-maps-markersitemizedoverlay

Android MapView custom markers are all the same size


I am writing an app that will show a map with a bunch of markers on it. I want the markers to be dynamic (showing dynamic content on them). Because the markers content is dynamic the size of each marker is different.

I found this in the documentation saying that "Different markers can be returned for different states. The different markers can have different bounds." here: https://developers.google.com/maps/documentation/android/reference/com/google/android/maps/OverlayItem#getMarker(int)

I assume that means that multiple markers on the same overlay can be different sizes. Is that correct?

I have an ArrayList on my overlay (extends BalloonItemizedOverlay). The overlay's createItem() method looks like this:

@Override
protected OverlayItem createItem(final int index)
{
    final Person person = people.get(index);
    final GeoPoint gp = new GeoPoint(person.getLat(), person.getLng());

    final OverlayItem oi = new OverlayItem(gp, person.getName(), person.getName());
    oi.setMarker(new PersonDrawable(defaultMarker, person));

    return oi;
}

Then in my PersonDrawable there is a drawable that has a 9 patch as a background image, and then drawing text on top of it:

public PersonDrawable(final Drawable iBackground, final Person person)
{
    background = iBackground;
    text = person.getName();
    padding = new Rect();

    if (background instanceof NinePatchDrawable)
    {
        // This is getting hit, and just sets the value of the padding
        final NinePatchDrawable ninePatch = (NinePatchDrawable) background;
        ninePatch.getPadding(padding);
    }

    // set the bounds of the marker
    bounds = background.getBounds();

    paint = new Paint();
    paint.setColor(Color.rgb(255, 255, 255));
    paint.setFakeBoldText(true);
    paint.setTextSize(30);

    // find the bounds of the text
    final Rect textBounds = new Rect();
    paint.getTextBounds(text, 0, text.length(), textBounds);

    // set the left and right bounds to that of the text plus the padding (then center it)
    bounds.left = 0 - (textBounds.width() / 2) - padding.left;
    bounds.right = 0 + (textBounds.width() / 2) + padding.right;

    // set the bounds of the drawable
    setBounds(bounds);
}

@Override
public void draw(final Canvas canvas)
{
    // when it's time to draw, draw the background
    background.draw(canvas);
    // then draw the text within the padding
    canvas.drawText(text, bounds.left + padding.left, bounds.bottom - padding.bottom, paint);
}

Each marker has a distinct bounds set, even after it's drawn on the overlay its bounds are distinct. The MapView (or Overlay) is not reusing the objects from createItem(). However the Overlay seems to always pick one marker and assume that is the size for all the markers. Is that just the behavior? Is there something I can do to make it respect the bounds differently for each marker?

Here are some pictures, when it decides to pick a larger marker things turn out ok:

Wolverine is longer so things look alright

However when it chooses to use the smaller marker as the size:

Beast is a little shorter and overflow


Solution

  • The problem is that when a Drawable is loaded from resources, it is treated as a shareable resources. From the Drawable documentation:

    By default, all drawables instances loaded from the same resource share a common state; if you modify the state of one instance, all the other instances will receive the same modification.

    In the OverlayItem, it is assuming that the Drawable is going to be the same size throughout. The effect you are seeing is that the last modification to the Drawable is overriding all previous modifications, so all displays of the Drawable are showing up exactly the same.

    The solution is to prevent sharing of the Drawable's modifications. You can do this using the mutate() method in conjunction with multiple loads of the Drawable from a resource. You should do this in conjunction with the PersonDrawable constructor you have outlined above.