Reputation: 9301
What would be the correct way of creating map markers from bitmap?
I'm using Google Maps Android v1 and moving to v2 now.
My map contains multiple markers that are created from bitmap.
Each marker consists of bitmap image and some text on it that could be different for each marker. So I have to modify each bitmap in memory.
I load marker images like this:
private Bitmap getMarker(final String name) {
Bitmap result = BitmapFactory.decodeFile(context.getFilesDir() + "/markers/" + name + ".png");
if (result == null) {
// must make a mutable copy as by default resource is returned immutable
result = decodeResource(context.getResources(), R.drawable.default_marker).copy(Config.ARGB_8888, true);
} else {
result = result.copy(Config.ARGB_8888, true);
}
return result;
}
And then apply custom text on it with canvas:
private void applyText(Bitmap marker, Paint paint, String text) {
Canvas canvas = new Canvas(marker);
canvas.drawText(text, calculateTextX(marker, paint, text), calculateTextY(marker, paint, text), paint);
}
Marker images are ~5KB size multicolor PNG files on MDPI devices.
With Google Maps Android v1
from time to time on some devices (hard to reproduce) I got java.lang.OutOfMemoryError
while decoding Bitmap image. And I have a feeling that I'm doing something wrong...
And I would like to know for sure that with Google Maps Android v2
I wont get the same problem.
Upvotes: 2
Views: 4275
Reputation: 10005
I have had the same issue a few days ago when switching to v2. I think the most important thing is to try to load the images in memory as less as possible, and therefore to keep in memory your marker original Bitmap as long as you need it.
Here is a sample of code showing how I did it:
public class MyMarkerFactoryFactory {
private Context ctx;
private Bitmap cachedMarkerBase; // Cached bitmap
private Bitmap currentMarker; // Working copy
private final List<Marker> markers = new ArrayList<Marker>();
public MyMarkerFactoryFactory(Context ctx, String markerName, int markerWidth, int markerHeight) {
this.ctx = ctx;
Bitmap src = BitmapFactory.decodeFile(ctx.getFilesDir() + "/markers/" + markerName + ".png");
int srcBitmapWidth = src.getWidth();
int srcBitmapHeight = src.getHeight();
if (markerWidth != srcBitmapWidth && markerHeight != srcBitmapHeight) {
// The rendering bitmap will depend on provided width and height
// In my case it's because the bitmap does not have the same pixel size
// depending on the display pixel density. So I've declared the size I
// I want in "dp" somewhere else and fetch it from ctx.getDimen
// createScaledBitmap will return the same bitmap if you are scaling
// to the same size, so it's good to test the size before you rescale
// otherwise you are likely to recycle() the bitmap you wanted to use.
cachedMarkerBase = Bitmap.createScaledBitmap(src, markerWidth, markerHeight, false);
src.recycle();
} else {
cachedMarkerBase = src;
}
currentMarker = cachedMarkerBase.copy(cachedMarkerBase.getConfig(), true);
}
public void onDestroy() {
clearOverlays();
if (cachedMarkerBase != null) {
cachedMarkerBase.recycle();
cachedMarkerBase = null;
}
if (currentMarker != null) {
currentMarker.recycle();
currentMarker = null;
}
}
public void clearOverlays() {
for (Marker marker : markers) {
marker.remove();
}
markers.clear();
}
public void addOverlay(GoogleMap map, LatLng position, String myText) {
drawMarkerWith(myText);
BitmapDescriptor bitmapDescriptor = BitmapDescriptorFactory.fromBitmap(currentMarker);
markers.add(map.addMarker(new MarkerOptions().anchor(0.5f, 0.5f).icon(bitmapDescriptor).position(position)));
}
private void drawMarkerWith(String myText) {
// Copy image from cache
for (int i = 0; i < cachedMarkerBase.getWidth(); i++) {
for (int j = 0; j < cachedMarkerBase.getHeight(); j++) {
currentMarker.setPixel(i, j, cachedMarkerBase.getPixel(i, j));
}
}
int x = currentMarker.getWidth() / 2, y = currentMarker.getHeight() / 2;
Paint paintForText = new Paint()
paintForText.setTextSize(7f); // TODO
// Draw text
Canvas canvas = new Canvas(currentMarker);
int x = 0; // TODO
int y = 0; // TODO
canvas.drawText(myText, x, y, paintForText);
}
}
This works well, of course, if you have a limited number of different markers. Just create the factory when your activity starts, and destroy it when it stops, and you'll only have two bitmaps in memory, and avoid loading/releasing a lot of bitmaps.
Upvotes: 2