How to draw a curved line between 2 points on canvas? - android

I have tried a lot of different approaches from examples around the web, but I can't seem to get this to work. I am trying to make a method that draws a curved line between 2 points on a canvas. The curve should be defined by a radius parameter.
Below is my current code.
public OverlayBuilder drawCurvedArrow(int startX, int startY, int endX, int endY, int curveRadius, int padding, int color) {
PointF mPoint1 = new PointF(startX, startY);
PointF mPoint2 = new PointF(endX, endY);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(12);
paint.setColor(color);
Path myPath = new Path();
myPath.moveTo(startX, startY);
myPath.quadTo(mPoint1.x, mPoint1.y, mPoint2.x, mPoint2.y);
canvas.drawPath(myPath, paint);
return this;
}
Edit
The problem is that I can't figure out how to curve the line that is drawn on the canvas.

I found a solution to my problem myself. Even though there were some great answers, they weren't an exact solution to my particular problem.
Here is what I did:
Found the point in between the 2 given points
Calculated the angle 90 degrees between the 2 points
Calculated the point X pixels from the middle point using the calculated degree from before.
Used "path.cubicTo" with these 3 points (Takes both negative and positive values to determine which way the line should curve).
Here is my code if anyone else should run into the same problem:
public OverlayBuilder drawCurvedArrow(int x1, int y1, int x2, int y2, int curveRadius, int color, int lineWidth) {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(lineWidth);
paint.setColor(ContextCompat.getColor(context, color));
final Path path = new Path();
int midX = x1 + ((x2 - x1) / 2);
int midY = y1 + ((y2 - y1) / 2);
float xDiff = midX - x1;
float yDiff = midY - y1;
double angle = (Math.atan2(yDiff, xDiff) * (180 / Math.PI)) - 90;
double angleRadians = Math.toRadians(angle);
float pointX = (float) (midX + curveRadius * Math.cos(angleRadians));
float pointY = (float) (midY + curveRadius * Math.sin(angleRadians));
path.moveTo(x1, y1);
path.cubicTo(x1,y1,pointX, pointY, x2, y2);
canvas.drawPath(path, paint);
return this;
}
And here is an example of how the implementation looks like:

I think you are using wrong method for this purpose, one of the solutions that I can suggest is below
float radius = 20;
final RectF oval = new RectF();
oval.set(point1.x - radius, point1.y - radius, point1.x + radius, point1.y+ radius);
Path myPath = new Path();
myPath.arcTo(oval, startAngle, -(float) sweepAngle, true);
and for calculation of startAngle you will need
int startAngle = (int) (180 / Math.PI * Math.atan2(point.y - point1.y, point.x - point1.x));
for sweepAngle you can find detailed description here.

Suppose you have two points mPoint1 and mPoint2
int w=canvas.getWidth();
int h=canvas.getHeight();
int w_2= (w / 2);
int h_2= (h / 2);
PointF mPoint1 = new PointF(0, 0); //starts at canvas left top
PointF mPoint2 = new PointF(w_2, h_2);//mid of the canvas
Path drawPath1 =drawCurve(mPoint1, mPoint2);
canvas.drawPath(drawPath1, paint);
Method to draw the line
private Path drawCurve(PointF mPointa, PointF mPointb) {
Path myPath = new Path();
myPath.moveTo(mPointa.x, mPointa.y);
final float x2 = (mPointb.x + mPointa.x) / 3;
final float y2 = (mPointb.y + mPointa.y) / 3;
myPath.quadTo(x2, y2, mPointb.x, mPointb.y);
return myPath;
}

Related

How do I draw a resizable pentagon shape with user touch events?

I am trying to create a pentagon shaped polygon which can be resized by dragging one of its vertices. I tried some code from this question on SO but later realised that the code is specifically written for a rectangle shape. I tried modifying the code in the onDraw() method but later refactored it to the way it was earlier after realising that it is of no use.
Any help?
So here's a code I just wrote to draw a pentagon in the canvas. You pretty much have the vertices which you can edit on the onTouchEvent.
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int heigth = getHeight();
// not required, but just to move it to the middle
float centerX = width / 2.f;
float centerY = heigth / 2.f;
// vertices
float p1x = 100.f + centerX;
float p1y = 0.f + centerY;
float p2x = 0.f + centerX;
float p2y = -80.f + centerY;
float p3x = -100.f + centerX;
float p3y = 0.f + centerY;
float p4x = -80.f + centerX;
float p4y = 80f + centerY;
float p5x = 80.f + centerX;
float p5y = 80.f + centerY;
Path path = new Path();
path.moveTo(p1x, p1y);
path.lineTo(p2x, p2y);
path.lineTo(p3x, p3y);
path.lineTo(p4x, p4y);
path.lineTo(p5x, p5y);
path.lineTo(p1x, p1y);
Paint paint = new Paint();
paint.setColor(Color.BLUE);
canvas.drawPath(path, paint);
}

Draw circle on start and end point of an Arc

Hi im having difficulties on drawing dots on arc's both ends (start and end)
Although I can draw arc on canvas. Heres my sample code for drawing arc.
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float width = (float) getWidth();
float height = (float) getHeight();
float radius;
if (width > height) {
radius = height / 4;
} else {
radius = width / 4;
}
float center_x, center_y;
final RectF oval = new RectF();
center_x = width / 2;
center_y = height / 2;
oval.set(center_x - radius,
center_y - radius,
center_x + radius,
center_y + radius);
float percent = 25;
float arcRadius = 360;
float angle = arcRadius * (percent/100);
canvas.drawArc(oval, 270, 360, false, trackpaint);
canvas.drawArc(oval, 270, angle, false, arcPaint);
}
the only missing is putting circles on start and end points of the arc. I've tried this link but it doest work Calculate Arc Center Point, Knowing It's Start and End Degrees. Any help will be much appreciated. Thank you
the coordinate of the start point is:
double startX = Math.cos(Math.toRadians(270)) * radius + center_x;
double startY = Math.sin(Math.toRadians(270)) * radius + center_y;
the coordinate of the end point is:
double endX = Math.cos(Math.toRadians(270 + angle)) * radius + center_x;
double endY = Math.sin(Math.toRadians(270 + angle)) * radius + center_y;
and then you can draw circle using the start point and end point:
canvas.drawCircle(startX, startY, 10, paint);
canvas.drawCircle(endX, endY, 10, paint);
Get path from the ARC
Use PathMeasure class to retrieve Path length and path TAN, using starting or Ending X and Y coordinates of the ARC
Use this X and Y coordinates to draw circle.
Example for circle at the start of the ARC:
final Path mPath = new Path();
mPath.addArc(oval, startAngle, sweepAngle);
PathMeasure pm = new PathMeasure(mPath, false);
float[] xyCoordinate = { arcStarting.x , arcStarting.y };
float pathLength = pm.getLength();
pm.getPosTan(0, xyCoordinate, null);//"0 for starting point"
PointF point = new PointF(xyCoordinate[0], xyCoordinate[1]);
canvas.drawCircle(point.x, point.y, 10, YourPaintHere)

Android - calculate arc angle

I have an arc and i wish to draw scale marks at 0, 45, 90, 135, 180 degrees, can anyone help me with the math needed to achive the x,y of points 5 and 30 on this sketch?:
here is my code for drawing the 1 scale mark.
private void drawScale(Canvas canvas) {
//canvas.drawOval(scaleRect, scalePaint);
canvas.save();
Paint p = new Paint();
p.setColor(Color.WHITE);
p.setStrokeWidth(10f);
canvas.drawLine(rectF.left-getWidth()/20, rectF.height()/2, rectF.left, rectF.height()/2, p);
canvas.restore();
}
You can calculate its rotation using sin and cos. Lets assume that you have zero point A and want to rotate it to point B which is rotated for 30°.
Something like this:
Basically new point is at (cx+x,cy+y). In this particular case definition of sin and cos would be next:
sin = x/R
cos = y/R
It is not hard to get exact x and y. So to rotate point on particular angle in circle with know radius we need to calculate coordinates in next way:
x = cx + sin(angle) * R;
y = cy + cos(angle) * R;
Now lets get back to Android and Canvas!
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
float cx = getWidth() / 2f;
float cy = getHeight() / 2f;
float scaleMarkSize = getResources().getDisplayMetrics().density * 16; // 16dp
float radius = Math.min(getWidth(), getHeight()) / 2;
for (int i = 0; i < 360; i += 45) {
float angle = (float) Math.toRadians(i); // Need to convert to radians first
float startX = (float) (cx + radius * Math.sin(angle));
float startY = (float) (cy - radius * Math.cos(angle));
float stopX = (float) (cx + (radius - scaleMarkSize) * Math.sin(angle));
float stopY = (float) (cy - (radius - scaleMarkSize) * Math.cos(angle));
canvas.drawLine(startX, startY, stopX, stopY, scalePaint);
}
canvas.restore();
}
Code will draw marks with step of 45°. Note you need to convert angle to radians and for Y axis I used minus cause on canvas it is flipped. Here is what I have got:
If you know the point at the center of the circle and the radius of the circle it becomes pretty easy if you use vectors to your advantage.
First you're gonna need the unit vectors at each angle
0 deg -> (-1,0)
45 deg -> (-1/sqrt(2), (1/sqrt(2))
90 deg -> (0,1)
135 deg -> (1/sqrt(2), (1/sqrt(2))
180 deg -> (1,0)
You can then calculate the necessary points using the formula below
point = center + (unit vector * distance from center)
Here is a more concrete example since andrew added one.
private static final float RADIUS = 400.0f;
private static final float MARK_LENGTH = 30.0f;
private static final UnitVector[] UNIT_VECTORS = new UnitVector[] {
new UnitVector(-1,0), // 0 deg
new UnitVector((float) (-1/Math.sqrt(2)), (float) (1/Math.sqrt(2))), // 45 deg
new UnitVector(0, 1), // 90 deg
new UnitVector((float) (1/Math.sqrt(2)), (float) (1/Math.sqrt(2))), // 135 deg
new UnitVector(1, 0), // 180 deg
new UnitVector((float) (1/Math.sqrt(2)), (float) (-1/Math.sqrt(2))), // 225 deg
new UnitVector(0, -1), // 270 deg
new UnitVector((float) (-1/Math.sqrt(2)), (float) (-1/Math.sqrt(2))), // 315 deg
};
static class UnitVector {
final float x;
final float y;
UnitVector(final float x, final float y) {
this.x = x;
this.y = y;
}
}
// Call this from onDraw
public void drawMarks(final Canvas canvas) {
for (final UnitVector unitVector : UNIT_VECTORS) {
this.drawMarkWithVector(unitVector, canvas);
}
}
private void drawMarkWithVector(final UnitVector unitVector, final Canvas canvas) {
final float centerPointX = this.getWidth() / 2;
final float centerPointY = this.getHeight() / 2;
final float startX = centerPointX + (unitVector.x * RADIUS);
final float startY = centerPointY + (unitVector.y * RADIUS);
final float endX = centerPointX + (unitVector.x * (RADIUS + MARK_LENGTH));
final float endY = centerPointY + (unitVector.y * (RADIUS + MARK_LENGTH));
canvas.drawLine(startX, startY, endX, endY, this.paint);
}
Here is the result of the code above

Android fill in part of a path?

I have a shape that I'm drawing with a path. I'm filling that shape in with a gradient and then I need to put another gray area ontop of that gradient dependent upon a %. I'm using path.quadTo to make my shape so I don't know the y coordinate of the top line to properly intersect it. This is what I'm getting when I just set it to the maximum y:
The white stroke is the image I'm trying to partially fill in. The right gray area I want to keep, but I need to get rid of the left gray area. Any ideas? This is what I'm trying so far:
#Override
public void onDraw(Canvas canvas) {
Path path = new Path();
Path grayPath = new Path();
float x1,y1,x3,y3,x2,y2;
float x1g,x2g;
int width = canvas.getWidth();
int height = canvas.getHeight();
gradientPaint.setShader(new LinearGradient(0, height,width,height, new int[]{Color.RED, Color.YELLOW, Color.GREEN}, new float[] {0,0.6f,1}, Shader.TileMode.REPEAT));
x1 = 0;
y1 = (float) (height * .90);
x2 = (float) (width * .75);
y2 = (float) (height * .50);
x3 = width;
y3 = (float) (height * .10);
x2g = (float) (width*.50);
//Ramp
path.moveTo(x1, y1);
path.quadTo(x2, y2, x3, y3);
//Down
path.lineTo(x3, y1+50);
//Back
path.lineTo(x1, y1+50);
//Up
path.lineTo(x1, y1);
//Ramp
grayPath.moveTo(x1, y1);
grayPath.quadTo(x2, y2, x3, y3);
//Down
grayPath.lineTo(x3, y1+50);
//Back
grayPath.lineTo(x2g, y1+50);
//Up
grayPath.lineTo(x2g, y3);
grayPath.setFillType(FillType.WINDING);
//Draw for shiny fill
//canvas.drawPath(path, gradientPaint);
//Draw for grayness
canvas.drawPath(grayPath, grayPaint);
//Draw for stroke!
canvas.drawPath(path, strokePaint);
}
Clipping is what I was looking for and is a much simpler solution:
#Override
public void onDraw(Canvas canvas) {
Path path = new Path();
float x1,y1,x3,y3,x2,y2;
int width = canvas.getWidth();
int height = canvas.getHeight();
gradientPaint.setShader(new LinearGradient(0, height,width,height, new int[]{Color.RED, Color.YELLOW, Color.GREEN}, new float[] {0,0.6f,1}, Shader.TileMode.REPEAT));
//Start at the left side, 10% up
x1 = 0;
y1 = (float) (height * .90);
x2 = (float) (width * .75);
y2 = (float) (height * .50);
x3 = width;
y3 = (float) (height * .10);
//Ramp
path.moveTo(x1, y1);
path.quadTo(x2, y2, x3, y3);
//Down
path.lineTo(x3, y1+50);
//Back
path.lineTo(x1, y1+50);
//Up
path.lineTo(x1, y1);
//Create Gray Rect with %
Rect rect = new Rect((int)(width*.50),0,(int) x3, (int) y1+50);
//CLIP IT
canvas.clipPath(path);
//Draw for shiny fill
canvas.drawPath(path, gradientPaint);
//Draw for grayness
canvas.drawRect(rect, grayPaint);
//Draw for stroke!
canvas.drawPath(path, strokePaint);
}

Drawing an arc that is part of a circle

I want to draw an arc using the method: .Canvas.drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)
I have two points on the circle and the circle center point and radius.
What exactly do I need to set in the oval rect and how to calculate the startAngle and sweepAngle?
I tried the code below:
m_radiusRect.set(x1, Math.min(y1, y2), x2, Math.max(y1,y2));
float startAngle = (float)((Math.toDegrees( Math.atan2(x1 - 360.0, 360.0 - y1) ) + 360.0) % 360.0);
float sweepAngle = (float)((Math.toDegrees( Math.atan2(x2 - 360.0, 360.0 - y2) ) + 360.0) % 360.0) - startAngle;
canvas.drawArc(m_radiusRect, startAngle, sweepAngle, false, m_paint);
See this answer and find the angle of a point from center.
Based on that find the two angles for x1 and x2 says a1 and a2.
then,
sweepAngle = a2 - a1;
startAngle = a1;
Edited
The formula given in link looks not working since it does not consider center.
Here center is (cx, cy)
float startAngle = (int) ((float) Math.toDegrees( Math.atan2(x1 - cx, y1 - cy)));
float sweepAngle = (int) ((float) Math.toDegrees( Math.atan2(x2 - cx, y2 - cy))) - startAngle;
Rect rect = new Rect();
rect.left = (int) (cx - radius);
rect.top = (int) (cy - radius);
rect.right = (int) (cx + radius);
rect.bottom = (int) (cy + radius);

Categories

Resources