Pavel Dudka Pavel Dudka - 6 months ago 31
Android Question

Stroke cap for custom PathEffect

I'm implementing custom path effect for route on top of MapView and I came up with the problem how to make my beginning and ending of the path rounded (like

Paint.setStrokeCap(Cap.ROUND)
does). See screenshot - black lines - is my route I want to round at the end

Here is how I implemented my custom PathEffect:

public RouteOverlay(Context context)
{
mContext = context;

mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(COLOR_DEFAULT);
mPaint.setAntiAlias(true);
mPaint.setStrokeCap(Cap.ROUND); // this one does not work...
mPaint.setStrokeJoin(Join.ROUND);
PathEffect e1 = new PathDashPathEffect(createRouteLineStyle(), 10, 3, PathDashPathEffect.Style.MORPH);
PathEffect e2 = new CornerPathEffect(10);
mPaint.setPathEffect(new ComposePathEffect(e1, e2));
}

private Path createRouteLineStyle()
{
Path p = new Path();
p.moveTo(-5, ROUTE_LINE_WIDTH/2);
p.lineTo(5,ROUTE_LINE_WIDTH/2);
p.lineTo(5,ROUTE_LINE_WIDTH/2-currentThickness);
p.lineTo(-5, ROUTE_LINE_WIDTH/2-currentThickness);
p.close();
p.moveTo(-5, -(ROUTE_LINE_WIDTH/2));
p.lineTo(5,-(ROUTE_LINE_WIDTH/2));
p.lineTo(5, -(ROUTE_LINE_WIDTH/2-currentThickness));
p.lineTo(-5, -(ROUTE_LINE_WIDTH/2-currentThickness));
return p;
}

@Override
public void draw(Canvas canvas, final MapView mapView, boolean shadow)
{
if(shadow) return;

if(mDrawEnabled)
{
synchronized(mPoints)
{
canvas.drawPath(mPath, mPaint);
}
}
}


As you can see on the screenshot, the ending of the line is not rounded (as well as beginning...).
setStrokeCap(Cap.ROUND)
doesn't help.

So the question is - how to add round cap to my custom path? I was thinking of using
addArc()
or
addCircle()
to the end (and beginning) of my path, but this doesn't seem right.

The reason why I need custom path effect - is that I need to draw the route around actual road - so route should be empty inside and have inner and outer stroke lines.

In case somebody knows how to make this kind of path effect in some other way - please let me know, because this solution has big cons I have to deal with..

No stroke cap

Answer

I managed to find a solution for my problem. So I got rid of my custom path effect and started to use usual stroke (where stroke cap works as expected). So I basically draw my path 2 times: at first I draw black line, after that I draw thiner transparent line to clear the center of previous black line.

The only trick in this approach is that I need to draw my path in a separate bitmap (using temp canvas) and when path bitmap is ready - render it to the main canvas.

@Override
public void draw(Canvas canvas, final MapView mapView, boolean shadow)
{
    //Generate new bitmap if old bitmap doesn't equal to the screen size (f.i. when screen orientation changes)
    if(pathBitmap == null || pathBitmap.isRecycled() || pathBitmap.getWidth()!=canvas.getWidth() || pathBitmap.getHeight()!=canvas.getHeight())
    {
        if(pathBitmap != null)
        {        
            pathBitmap.recycle();
        }
        pathBitmap = Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Config.ARGB_8888);
        tempCanvas.setBitmap(pathBitmap);
    }

    //Render routes to the temporary bitmap
    renderPathBitmap();

    //Render temporary bitmap onto main canvas
    canvas.drawBitmap(pathBitmap, 0, 0, null);
    }
}

private void renderPath(Path path, Canvas canvas)
{
    routePaint.setStrokeWidth(ROUTE_LINE_WIDTH);
    routePaint.setColor(OUTER_COLOR);
    routePaint.setXfermode(null);

    canvas.drawPath(path, routePaint); //render outer line

    routePaint.setStrokeWidth(ROUTE_LINE_WIDTH/1.7f);
    routePaint.setColor(Color.TRANSPARENT);
    routePaint.setXfermode(new PorterDuffXfermode(Mode.CLEAR));

    canvas.drawPath(path, routePaint); //render inner line
}

So result looks like:

enter image description here