10

I'm currently working on map overlay which highlights the route along specified points and I need to implement certain line style (something like on screenshot)

route indication What I'm trying to do - is to highlight the route with something like transparent line with black stroke lines from both sides

Playing with different fill styles and Paint settings haven't led me to any solution so far.

Does anybody know what direction I need to look for?

Currently I managed to draw only solid line, but this is not what I'm looking for:

Paint setup:

mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(COLOR_DEFAULT);
mPaint.setPathEffect(new CornerPathEffect(10));
mPaint.setStrokeWidth(6);
mPaint.setAntiAlias(true);

Drawing routine

canvas.drawPath(mPath, mPaint);
Pavel Dudka
  • 20,754
  • 7
  • 70
  • 83

2 Answers2

34

I get pretty good results with PathDashPathEffect using a "dash stamp" that's two very thin rectangles and the MORPH style option. See last and 3rd last line here:

enter image description here

This was drawn by modifying the PathEffects example in ApiDemos taken from the SDK:

package com.example.android.apis.graphics;

import android.content.Context;
import android.graphics.*;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;

public class PathEffects extends GraphicsActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new SampleView(this));
    }

    private static class SampleView extends View {
        private Paint mPaint;
        private Path mPath;
        private PathEffect[] mEffects;
        private int[] mColors;
        private float mPhase = 3;

        private static void makeEffects(PathEffect[] e, float phase) {
            e[0] = null;     // no effect
            e[1] = new CornerPathEffect(10);
            e[2] = new DashPathEffect(new float[] {10, 5, 5, 5}, phase);
            e[3] = new PathDashPathEffect(makePathDash(), 12, phase,
                                          PathDashPathEffect.Style.MORPH);
            e[4] = new ComposePathEffect(e[2], e[1]);
            e[5] = new ComposePathEffect(e[3], e[1]);
        }

        public SampleView(Context context) {
            super(context);
            setFocusable(true);
            setFocusableInTouchMode(true);

            mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setStrokeWidth(6);

            mPath = makeFollowPath();

            mEffects = new PathEffect[6];

            mColors = new int[] { Color.BLACK, Color.RED, Color.BLUE,
                                  Color.GREEN, Color.MAGENTA, Color.BLACK
                                };
        }

        @Override protected void onDraw(Canvas canvas) {
            canvas.drawColor(Color.WHITE);

            RectF bounds = new RectF();
            mPath.computeBounds(bounds, false);
            canvas.translate(10 - bounds.left, 10 - bounds.top);

            makeEffects(mEffects, mPhase);
            invalidate();

            for (int i = 0; i < mEffects.length; i++) {
                mPaint.setPathEffect(mEffects[i]);
                mPaint.setColor(mColors[i]);
                canvas.drawPath(mPath, mPaint);
                canvas.translate(0, 28);
            }
        }

        @Override public boolean onKeyDown(int keyCode, KeyEvent event) {
            switch (keyCode) {
                case KeyEvent.KEYCODE_DPAD_CENTER:
                    mPath = makeFollowPath();
                    return true;
            }
            return super.onKeyDown(keyCode, event);
        }

        private static Path makeFollowPath() {
            Path p = new Path();
            p.moveTo(0, 0);
            for (int i = 1; i <= 15; i++) {
                p.lineTo(i*20, (float)Math.random() * 35);
            }
            return p;
        }

        private static Path makePathDash() {
            Path p = new Path();
            p.moveTo(-6, 4);
            p.lineTo(6,4);
            p.lineTo(6,3);
            p.lineTo(-6, 3);
            p.close();
            p.moveTo(-6, -4);
            p.lineTo(6,-4);
            p.lineTo(6,-3);
            p.lineTo(-6, -3);
            return p;
        }
    }
}
Gene
  • 46,253
  • 4
  • 58
  • 96
  • Nice answer! thanks! What if I want to set the background color of the last line? Is that possible? I would like to make the border black and fill it with blue – Felipe Mosso Jul 14 '13 at 17:39
  • Can we increase the width of the lines in the last line of the image? – user1294668 Aug 23 '13 at 07:36
  • @user1294668 Sure, but I don't know how smooth the result will look. The `makePathDash` method is drawing what looks like an equal sign where the the strokes are rectangles 2 pixels tall (y-coordinates +-3 and +- 4). Just make them taller. – Gene Aug 23 '13 at 14:34
  • @Gene Here's the method I'm using. It's increasing the spacing between the lines. I want the stroke width of both to be increased. How to do it? `private static Path makePathDash() { Path p = new Path(); p.moveTo(-6, 8); p.lineTo(6,8); p.lineTo(6,7); p.lineTo(-6, 7); p.close(); p.moveTo(-6, -8); p.lineTo(6,-8); p.lineTo(6,-7); p.lineTo(-6, -7); return p; }` – user1294668 Aug 24 '13 at 05:01
  • 1
    @user1294668 You want something like (-6,6) (6,6) (6,3) (-6,3) and similar for the other side. – Gene Aug 24 '13 at 06:01
  • @Gene Ah. I get it. Thanks for the help. One more thing I'd like to ask, how to have rounded corners at both ends of the line? – user1294668 Aug 24 '13 at 06:04
  • The little segment of double strokes is just an Android path. You can try defining the little strokes to have semi-circular ends rather than square ones. See the Path documentation http://developer.android.com/reference/android/graphics/Path.html Two of the `lineTo`s will be replaced by `addArc`. – Gene Aug 24 '13 at 16:08
1

I managed to find a better solution for my problem. So I got rid of my custom path effect and started to use usual stroke. 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.

Hope this will help somebody else

@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

Pavel Dudka
  • 20,754
  • 7
  • 70
  • 83
  • I can't get it working on OSM maps. Here's the code I'm using. It doesn't draw anything. `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 = new Canvas(pathBitmap); } tempCanvas .drawPath(path, routePaint); canvas.drawBitmap(pathBitmap, 0, 0, null);` – user1294668 Aug 24 '13 at 05:12