Android MapView overlay, caching bitmap on draw
I have an overlay for drawing a path on my MapView
, but i noticed it gets unnecessarily redrawn about ten times per second. Since in the draw
method i draw every single segment of the path, this could easily be an efficiency issue.
For this reason i decided to cache the contents of the overlay and actually redraw it just when necessary, that is when the path changes, or the center of the map has moved, or the zoom level has changed.
Now, one of the parameters of the draw
method is the Canvas
to draw on. I know how to draw the cached bitmap on it, the problem is i don't know how to cache the content of the canvas on a bitmap.
I can't instantiate a new canvas, nor can i call setBitmap
because the canvas in a HardwareCanvas
and it throws an UnsupportedOperationException
if that method is invoked.
So, to recap, i have a canvas and a bitmap, how can i copy the canvas' content to the bitmap?
Edit
this is my draw method for clarity, i don't invoke it manually but still it gets called repeatedly even when the map is not moving at all
public void draw(Canvas canvas, MapView map, boolean shadow) {
if (shadow) {
// this overlay doesn't need to draw shadows
return;
}
if (paths.isEmpty()) {
// nothing to draw
return;
}
center = map.getMapCenter();
zoomLevel = map.getZoomLevel();
map.getDrawingRect(bounds);
projection = map.getProjection();
maxLevel = map.getMaxZoomLevel();
for (MapPath mp : paths) {
// adjust path width to current zoom
adjustedWidth = mp.getWidth() * zoomLevel / maxLevel;
if (adjustedWidth < MIN_WIDTH_TO_DRAW) {
// path is too thin, avoid drawing it
continue;
}
paint.setStrokeWidth(adjustedWidth);
paint.setColor(mp.getColor());
state = PathState.FIRST_POINT;
path.reset();
for (PathPoint pp : mp.getPoints()) {
if (!pp.shoudAppearAtZoomLevel(zoomLevel)) {
// do not draw this point at this zoom level
continue;
}
// project a geopoint to a pixel
projection.toPixels(pp.getGeoPoint(), point);
inside = isInsideBounds(point, map);
switch (state) {
case FIRST_POINT:
// move to starting point
firstX = point.x;
firstY = point.y;
path.moveTo(firstX, firstY);
break;
case WAS_INSIDE:
// segment is completely or partially on map
path.lineTo(point.x, point.y);
break;
case WAS_OUTSIDE:
if (inside) {
// segment is partially on map
path.lineTo(point.x, point.y);
} else {
// segment is completely off map
path.moveTo(point.x, point.y);
}
break;
}
// update state
state = inside ? PathState.WAS_INSIDE : PathState.WAS_OUTSIDE;
}
// workaround to avoid canvas becoming too big when path is mostly off screen
path.moveTo(firstX, firstY);
// draw this path to canvas
canvas.drawPath(path, paint);
}
super.draw(canvas, map, shadow);
}
You can't get the bitmap to where the Mapview
canvas is drawing.
The approach should be following:
MapView
canvas MapView
canvas. However the performance/efficiency issues you are refering are probably due to incorrect design of your existing solution. I can draw paths with 10.000 points without using bitmap (and there are a few good reasons to not use them) in about 3ms in a medium range device.
There are a few hints on how to approach it, on my answer to this post: Overlay.draw() calls many times. Check also the answer from @shkschneider in the same post.
--EDITED--
Just by looking at the code, I can't figure out why you are getting this warning ... But are making it much more complex then it needs to be.
Organize you code in the following way:
draw
The draw()
methos only checks if there is a zoom change (if so ask the path to be rebuild) and if map has moved (if so offset path) and finally draws the path.
@Override
public void draw(Canvas canvas, MapView mapview, boolean shadow) {
super.draw(canvas, mapview, shadow);
if(shadow) return;
if(mp.getPoints() == null || mp.getPoints().size() < 2) return;
Projection projection = mapview.getProjection();
int lonSpanNew = projection.fromPixels(0,mapview.getHeight()/2).getLongitudeE6() -
projection.fromPixels(mapview.getWidth(),mapview.getHeight()/2).getLongitudeE6();
if(lonSpanNew != pathInitialLonSpan)
pathBuild();
else{ //check if path need to be offset
projection.toPixels(mp.getPoints().get(0), p1);
if(p1.x != pathInitialPoint.x || p1.y != pathInitialPoint.y){
path.offset(p1.x - pathInitialPoint.x, p1.y - pathInitialPoint.y);
pathInitialPoint.x = p1.x;
pathInitialPoint.y = p1.y;
}
}
canvas.drawPath(path, paint);
}
pathBuild
The path has to be built every time zoom changes. The zoom change detection is done using pathInitialLonSpan
as getZoomLevel()
is not shyncronous with map zoom animation.
private void pathBuild(){
path.rewind();
if(mp.getPoints() == null || mp.getPoints().size() < 2) return;
Projection projection = mapView.getProjection();
pathInitialLonSpan = projection.fromPixels(0,mapView.getHeight()/2).getLongitudeE6() -
projection.fromPixels(mapView.getWidth(),mapView.getHeight()/2).getLongitudeE6();
projection.toPixels(mp.getPoints().get(0), pathInitialPoint);
path.moveTo(pathInitialPoint.x,pathInitialPoint.y);
for(int i=1; i<mp.getPoints().size(); i++){
projection.toPixels(mp.getPoints().get(i), p1);
int distance2 = (pPrev.x - p1.x) * (pPrev.x - p1.x) + (pPrev.y - p1.y) * (pPrev.y - p1.y);
if(distance2 > 9){
path.lineTo(p1.x,p1.y);
pPrev.set(p1.x, p1.y);
}
}
Some objects (ie p1, pPrev, etc) are defined at class level to avoid creating new ones everytime the methos runs.
Note:
I've changed the variable names to fit the ones you are using. I hope I've not made any mistake, but you should be able to figure that out.
Regards.
链接地址: http://www.djcxy.com/p/68572.html