Stroke cap for custom PathEffect - android

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..

I don't see any reason why that should not work unless you are running into the problem mentioned here http://code.google.com/p/android/issues/detail?id=24873.

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:

Related

Draw grid over polygon in osmdroid bonus pack

I have searched all internet but I don't find answer to my problem .I'm using osmdroid and I want to add grid over polygon as shown in image. I found one similar question in stackoverflow but this question doesn't have answer. So please tell me is that possible?
#Mker gave a good point to start: BitmapShader.
Here is a sample code:
public class GridPolygon extends Polygon {
private BitmapShader bitmapShader;
public GridPolygon(Context ctx) {
super(ctx);
}
public void setPatternBMP(#NonNull final Bitmap patternBMP) {
bitmapShader = new BitmapShader(patternBMP, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
mFillPaint.setShader(bitmapShader);
}
}
Usage:
final GridPolygon polygon = new GridPolygon(context);
polygon.setPoints(geoData);
polygon.setFillColor(fillColor);
polygon.setStrokeColor(strokeColor);
polygon.setStrokeWidth(strokeWidth);
polygon.setPatternBMP(BitmapFactory.decodeResource(getResources(), R.drawable.pattern));
map.getOverlays().add(polygon);
map.invalidate();
But you might be confused if you tried to move the polygon - the bitmap doesn't want to move:
To avoid this you should calculate the offset for your shader:
public class GridPolygon extends Polygon {
private BitmapShader bitmapShader;
private IGeoPoint lastCenterGeoPoint;
private int xOffset = 0;
private int yOffset = 0;
public GridPolygon(Context ctx) {
super(ctx);
}
public void setPatternBMP(#NonNull final Bitmap patternBMP) {
bitmapShader = new BitmapShader(patternBMP, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
mFillPaint.setShader(bitmapShader);
}
protected void recalculateMatrix(#NonNull final MapView mapView) {
//final int mapSize = TileSystem.MapSize(mapView.getZoomLevel());
final Projection projection = mapView.getProjection();
final IGeoPoint geoPoint = mapView.getMapCenter();
if (lastCenterGeoPoint == null) lastCenterGeoPoint = geoPoint;
final Point point = projection.toPixels(geoPoint, null);
final Point lastCenterPoint = projection.toPixels(lastCenterGeoPoint, null);
xOffset += lastCenterPoint.x - point.x;
yOffset += lastCenterPoint.y - point.y;
xOffset %= 100; // 100 is pixel size of shader image
yOffset %= 100;
final Matrix matrix = new Matrix();
matrix.reset();
matrix.setScale(1,1);
matrix.preTranslate(xOffset, yOffset);
//matrix.setTranslate(xOffset, yOffset);
bitmapShader.setLocalMatrix(matrix);
mFillPaint.setShader(bitmapShader);
lastCenterGeoPoint = geoPoint;
}
#Override
protected void draw(Canvas canvas, MapView mapView, boolean shadow) {
recalculateMatrix(mapView);
super.draw(canvas, mapView, shadow);
}
}
Result:
Full source code.
Yes it's possible.
There's a few potential solutions.
1) Assuming someone was nice enough to make a kml file that meets your needs, the kml file can be directly imported using osmbonuspack.
2) Make it yourself programatically. So you have a few tasks.
a) Make the polygon as an overlay
b) Make the grid as an overlay
c) Add them to the map view in that order. This should make the grid be on top of the polygon.
Now on to the details. Making the polygon is trivial so won't cover this here.
Making the grid isn't too hard either. You need to know the bounds of the grid, then place lines from the east, west bounds at some interval from the north bounds to the south bounds. Then do the opposite for north south lines. There's special cases at the date line, equator, and poles so keep that in mind.
Calculating the line interval in this case is somewhat simple and you can tackle it two ways. Use a fixed interval in degrees decimal or calculate based on zoom level. The later part is harder but generally gives a better visualization (when you zoom in, the grid redraws and looks more appropriate at that zoom level).
Important note, with osmbonuspack and osmdroid, you may run into out of memory errors if you give the overlay lines that are way outside of the bounds of the view (if hardware acceleration is off). If hardware acceleration is on, then lines may not show at all if both the start and end points are off screen by a certain margin. Long story short, for relatively small distances, you should be fine, otherwise, you have to clip at the view bounds on map panning and zooming.
I've done similar things with osmbonuspack for displaying lat/lon grid lines that adjust as you zoom in and pan (meaning the interval adjusts based on on zoom level). If that's a requirement, then you might be able to just reuse the code, which essentially calculates about how far away and where to draw each line of the grid.
Now, if you just want to draw the grid as a pattern (no constraint about grid lines positions), there should be a simple alternative by using a "shader":
fillPaint.setShader(patternBMPshader);
Full example: http://code.tutsplus.com/tutorials/android-sdk-drawing-with-pattern-fills--mobile-19527
Bad news, there is no getter of the Polygon fill paint. Good news, the attribute is protected, not private.
So you can subclass Polygon, and add the getter:
Paint getFillPaint(){
return mFillPaint;
}

How to draw a lot of rectangles on canvas with good performance?

I've got to render a cinema hall map with sectors, rows and seats.
Currently I have around 1000 of seats (filled rectangles) to draw and I'm doing this for every seat by calling:
canvas.drawRect(seatRect, seatPaint)
My view must also support scaling, scrolling and fling.
The performance is awful. I tried to improve it by explicitly enabling hardware acceleration but nothing changed, it seems it was enabled by default on my Nexus 4 (Api 22)
Could you please suggest any methods to render huge number of rectangles at high speed ? So movement, scaling animation is smooth.
Custom View class code:
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.scale(mScaleFactor, mScaleFactor); // scaling support
canvas.translate(mTranslateX, mTranslateY); // scrolling support
if (mEventMap != null) {
mEventMap.paint(canvas);
}
canvas.restore();
}
EventMap code:
public void paint(Canvas canvas) {
for (EventPlace place : places) {
if (place.isSelected())
placePaint.setColor(0xFF00FF00);
else if (place.isAvailable())
placePaint.setColor(place.getColor());
else
placePaint.setColor(0xFF000000);
canvas.drawRect(place.getBounds(), placePaint);
}
}
None of the methods called in onDraw create any instances.
There are just a LOT of rectangles...
The main problem that you have right now is basically the fact that you perform a large amount of drawing cycles (equal to the number of Rects you have) it's better to store all those rects in Path object, in order to reduce number of drawing cycles. Since you have three states of seats I suggest creation of three Paths. I would suggest doing something like this:
private void init() {
mSelectedPath = new Path();
mAvailablePath = new Path();
mUnavalablePath = new Path();
mAvailablePaint = new Paint();
mSelectedPaint = new Paint();
mUnavalablePaint = new Paint();
mUnavalablePaint.setColor(Color.RED);
mSelectedPaint.setColor(Color.YELLOW);
mAvailablePaint.setColor(Color.GREEN);
for (EventPlace place : mData) {
if (place.isSelected())
mSelectedPath.addRect(rectF, Path.Direction.CW);
else if (place.isAvailable())
mAvailablePath.addRect(rectF, Path.Direction.CW);
else
mUnavalablePath.addRect(rectF, Path.Direction.CW);
}
}
Then when you want to associate date with this View you should do something like this:
public void setData(List<EventPlace> data) {
mData = data;
init();
invalidate();
}
Actually you can do it however you like, just keep in mind that you have to call init method and then invalidate. And in the onDraw:
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.scale(mScaleFactor, mScaleFactor); // scaling support
canvas.translate(mTranslateX, mTranslateY); // scrolling support
canvas.drawPath(mAvailablePath, mAvailablePaint);
canvas.drawPath(mUnavalablePath, mUnavalablePaint);
canvas.drawPath(mSelectedPath, mSelectedPaint);
canvas.restore();
}
Perhaps you would also want to add some logics to exclude those Rects that are not visible at the moment from paths in order to tweak performance.
I tried animating my approach on test set of Rects and it was running smoothly with 200 rects, I didn't try performing a serious benchmark though

how to erase smoothly in android?

i want the eraser to erase smoothly but in my activity by default it erase the lines on touch draw and erase the lines on touch is not proper.my code what i had worked on.
public void Draw(){
count=1;
bmp = Bitmap.createBitmap(fullimage2.getWidth(), fullimage2.getHeight(), Config.ARGB_8888);
c = new Canvas(bmp);
fullimage2.draw(c);
if(mode==0){
pnt.setColor(Color.BLACK);
pnt.setStyle(Paint.Style.STROKE);
pnt.setStrokeJoin(Paint.Join.ROUND);
pnt.setStrokeCap(Paint.Cap.ROUND);
pnt.setStrokeWidth(8);
c.drawPath(path,pnt);
}
else{
pnt1.setXfermode(new PorterDuffXfermode(
PorterDuff.Mode.CLEAR));
pnt1.setStrokeWidth(25);
pnt1.setStrokeCap(Paint.Cap.ROUND);
pnt1.setColor(Color.TRANSPARENT);
pnt1.setAlpha(0);
c.drawPath(path1,pnt1);
pnt1.setStyle(Style.STROKE);
pnt1.setMaskFilter(null);
pnt1.setAntiAlias(true);
}
Is the code you use to draw proper? Otherwise use the same code and for the erraser use the same color as your background. If you need better code to draw, ask!

Android MapView custom markers are all the same size

I am writing an app that will show a map with a bunch of markers on it. I want the markers to be dynamic (showing dynamic content on them). Because the markers content is dynamic the size of each marker is different.
I found this in the documentation saying that "Different markers can be returned for different states. The different markers can have different bounds." here: https://developers.google.com/maps/documentation/android/reference/com/google/android/maps/OverlayItem#getMarker(int)
I assume that means that multiple markers on the same overlay can be different sizes. Is that correct?
I have an ArrayList on my overlay (extends BalloonItemizedOverlay). The overlay's createItem() method looks like this:
#Override
protected OverlayItem createItem(final int index)
{
final Person person = people.get(index);
final GeoPoint gp = new GeoPoint(person.getLat(), person.getLng());
final OverlayItem oi = new OverlayItem(gp, person.getName(), person.getName());
oi.setMarker(new PersonDrawable(defaultMarker, person));
return oi;
}
Then in my PersonDrawable there is a drawable that has a 9 patch as a background image, and then drawing text on top of it:
public PersonDrawable(final Drawable iBackground, final Person person)
{
background = iBackground;
text = person.getName();
padding = new Rect();
if (background instanceof NinePatchDrawable)
{
// This is getting hit, and just sets the value of the padding
final NinePatchDrawable ninePatch = (NinePatchDrawable) background;
ninePatch.getPadding(padding);
}
// set the bounds of the marker
bounds = background.getBounds();
paint = new Paint();
paint.setColor(Color.rgb(255, 255, 255));
paint.setFakeBoldText(true);
paint.setTextSize(30);
// find the bounds of the text
final Rect textBounds = new Rect();
paint.getTextBounds(text, 0, text.length(), textBounds);
// set the left and right bounds to that of the text plus the padding (then center it)
bounds.left = 0 - (textBounds.width() / 2) - padding.left;
bounds.right = 0 + (textBounds.width() / 2) + padding.right;
// set the bounds of the drawable
setBounds(bounds);
}
#Override
public void draw(final Canvas canvas)
{
// when it's time to draw, draw the background
background.draw(canvas);
// then draw the text within the padding
canvas.drawText(text, bounds.left + padding.left, bounds.bottom - padding.bottom, paint);
}
Each marker has a distinct bounds set, even after it's drawn on the overlay its bounds are distinct. The MapView (or Overlay) is not reusing the objects from createItem(). However the Overlay seems to always pick one marker and assume that is the size for all the markers. Is that just the behavior? Is there something I can do to make it respect the bounds differently for each marker?
Here are some pictures, when it decides to pick a larger marker things turn out ok:
However when it chooses to use the smaller marker as the size:
The problem is that when a Drawable is loaded from resources, it is treated as a shareable resources. From the Drawable documentation:
By default, all drawables instances loaded from the same resource share a common state; if you modify the state of one instance, all the other instances will receive the same modification.
In the OverlayItem, it is assuming that the Drawable is going to be the same size throughout. The effect you are seeing is that the last modification to the Drawable is overriding all previous modifications, so all displays of the Drawable are showing up exactly the same.
The solution is to prevent sharing of the Drawable's modifications. You can do this using the mutate() method in conjunction with multiple loads of the Drawable from a resource. You should do this in conjunction with the PersonDrawable constructor you have outlined above.

How to remove paints into surfaceview in android

Here I need to remove paints.I did paints using surfaceview.inside erase button I use below code. Now when I click erase button the drawn paints all erased.But now again draw means paints not visible.please any one help me.
public void onClick(View view){
if(view==erasebtn)
{
if (!currentDrawingPath.isEmpty()) {
currentPaint .setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
action=true;
}
}
}
If you want to completely erase all drawing you have to fill it with the "empty" color.
Assuming you have a canvas in which you draw:
canvas.drawColor(Color.WHITE);
If you have drawn lines etc in a Canvas where you just add your drawings all the time then you need to create a way to restore an older version of that. Changing the Paint you have used to draw things will not change the things you have already drawn. It just affects how any future drawing is done.
There are several possibilities to do that e.g. the following should work:
Bitmap bitmap = Bitmap.createBitmap(400, 400, null);
Canvas canvas = new Canvas(bitmap);
ByteBuffer buffer = ByteBuffer.allocate(bitmap.getByteCount());
//save the state
bitmap.copyPixelsToBuffer(buffer);
// draw something
canvas.drawLine();
// restore the state
bitmap.copyPixelsFromBuffer(buffer):
That way you could go back 1 state. If you need to undo more steps think about saving Bitmaps to disk since it will consume quite a lot of memory otherwise.
Another possibility is to save all those steps you have drawn numerically in a list (like a vector graphic) in a way that you can redraw the full image up to a certain point - then you can just undo drawing by drawing just the first part of your list to a fresh image.
Edit: Would it work if you add this to the code and use it instead of undo()?
// add me to the code that has undo()
public void undoAll (){
final int length = currentStackLength();
for (int i = lenght - 1; i >= 0; i--) {
final DrawingPath undoCommand = currentStack.get( i );
currentStack.remove( i );
undoCommand.undo();
redoStack.add( undoCommand );
}
}

Categories

Resources