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:

  • First you create your own (empty and transparent) bitmap, with the same size as the MapView canvas
  • Then you create your won canvas for your bitmap (this canvas is the drawing tool that you are using to draw to your bitmap) and you draw the path using it.
  • Finally you draw your bitmap (with the path already drawn) to the 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

    上一篇: Android自定义叠加在带有边框和图像的mapview上?

    下一篇: Android MapView叠加,缓存绘制位图