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)
Related
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);
}
I have a custom view that I have created for Android where I am drawing a Circle and dividing it into sections.
Here is the code for onDraw:
int w = Width;
int h = Height;
int pl = PaddingLeft;
int pr = PaddingRight;
int pt = PaddingTop;
int pb = PaddingBottom;
int usableWidth = w - (pl + pr);
int usableHeight = h - (pt + pb);
int radius = Math.Min(usableWidth, usableHeight) / 2;
int cx = pl + (usableWidth / 2);
int cy = pt + (usableHeight / 2);
int lineLenght = radius - (pl * 2) - (pr * 2);
paint.Color = Color.Black;
paint.SetStyle(Paint.Style.Stroke);
canvas.DrawCircle(cx, cy, radius, paint);
//Move to top of the circle
float pointAngle = 360 / noOfJoints;
for (float angle = 0; angle < 361; angle = angle + pointAngle)
{ //move round the circle to each point
float x = cx + ((float)Math.Cos(radians(angle)) * radius); //convert angle to radians for x and y coordinates
float y = cy + ((float)Math.Sin(radians(angle)) * radius);
canvas.DrawLine(cx, cy, x, y, paint); //draw a line from center point back to the point
}
But when I run this, it provides a view like the following:
Which is close to what I want, but the start of sections should be from the middle. How can I get it to start from 0 angle (first divider should be from Top to bottom straight line).
The preferred circle is as follows:
Try this:
for (float angle = 0; angle < 361; angle = angle + pointAngle)
{ //move round the circle to each point
float displacedAngle = angle - 90;
float x = cx + ((float)Math.Cos(radians(displacedAngle)) * radius); //convert angle to radians for x and y coordinates
float y = cy + ((float)Math.Sin(radians(displacedAngle)) * radius);
canvas.DrawLine(cx, cy, x, y, paint); //draw a line from center point back to the point
}
Angle 0 is the rightest point of the circle, subtracting 90 will it move to top point.
Also, a recommendation, avoid variable creation and object instantiation as much as possible in the onDraw method. It's a real performance killer.
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;
}
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
I'm porting Skycons to Android and I've got most of them working, except the moon uses HTML5's Canvas.arc with the counterclockwise argument.
I've tried to implement this like so:
RectF rect = new RectF();
public void arcR( Path path, float x, float y, float radius, double startAngle, double endAngle, boolean anticlockwise ){
// Set bounds
rect.set( x - radius, y - radius, x + radius, y + radius );
// Convert to degrees
startAngle = Math.toDegrees(startAngle);
endAngle = Math.toDegrees(endAngle);
if(anticlockwise){
startAngle = 360 - startAngle;
endAngle = 360 - endAngle;
}
endAngle = endAngle - startAngle;
path.addArc(rect, (float)startAngle, (float)endAngle);
}
I don't think I've implemented counterclockwise correctly, as on my device drawing the moon (based on Skycons) looks like this:
For counter clockwise arcs, the sweep angle needs to be negative
float sweepAngle = ((endAngle + 360) - startAngle) % 360;
if (anticlockwise) {
sweepAngle = sweepAngle - 360;
}
path.addArc(rect, startAngle, sweepAngle);
double sweepAngle = endAngle - startAngle;
startAngle = 360 - endAngle;
path.addArc(rect, (float)startAngle, (float)sweepAngle);
To draw a cutout line like that you need to find the center of the second circle. I've assumed that the cutout circle radius would be the same as in the original circle, but of course you can change that.
Please try this code:
private void drawMoon(Path path, float x, float y, float radius, float startAngle, float endAngle) {
float sweepAngle = endAngle - startAngle;
float x1 = (float) (x + radius * Math.cos(Math.toRadians(startAngle)));
float y1 = (float) (y + radius * Math.sin(Math.toRadians(startAngle)));
float x2 = (float) (x + radius * Math.cos(Math.toRadians(endAngle)));
float y2 = (float) (y + radius * Math.sin(Math.toRadians(endAngle)));
float x3 = (x1 + x2) / 2;
float y3 = (y1 + y2) / 2;
float q = (float) Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
float r = radius; // cutout circle radius
float cx = (float) (x3 + Math.sqrt(r * r - (q / 2) * (q / 2)) * (y1 - y2) / q);
float cy = (float) (y3 + Math.sqrt(r * r - (q / 2) * (q / 2)) * (x2 - x1) / q);
path.rewind();
rect.set(x - radius, y - radius, x + radius, y + radius);
path.addArc(rect, startAngle, sweepAngle);
rect.set(cx - r, cy - r, cx + r, cy + r);
path.addArc(rect, (float) Math.toDegrees(Math.atan2(y1 - cy, x1 - cx)), 360 - sweepAngle);
}
differences between html5 canvas arc and android canvas arc:
in html5/javascript you give centerX, centerY, radius as first 3 arguments. In android, you give the rectangle enclosing the circle instead of those 3. That is a little more cumbersome for circles, but is more flexible, as you can then also do ovals (ellipse)
in javascript, you give start angle, end angle, and boolean for counterclockwise as arguments 4,5,6. In android, you give instead start angle and "sweep angle". The "sweep angle" is the difference between end angle and start angle. If you want counterclockwise, just give a negative sweep angle.
javascript angles are in radians, and in android they are in degrees.
As an illustration, here is some example code which draws a C - shaped figure consisting of a long arc on the left, and 3 short arcs on the right, one of which is counterclockwise.
JavaScript:
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="content-type" content="text/html" />
<title>arcs</title>
<script type="text/javascript">
function initPage(){
ctx = document.getElementById("canvas").getContext('2d');
ctx.fillStyle = "blue";
pi = Math.PI;
r = 100;
R = 3 * r;
padding = 5;
width = 2 * (R + padding);
height = width;
centerX = padding + width/2;
centerY = padding + height/2;
ctx.beginPath();
ctx.arc(centerX, centerY, R, pi/2, 3*pi/2, false);
ctx.arc(centerX, centerY - 2*r, r, 3*pi/2, pi/2, false);
ctx.arc(centerX, centerY, r, 3*pi/2, pi/2, true );
ctx.arc(centerX, centerY + 2*r, r, 3*pi/2, pi/2, false);
ctx.fill();
}
</script>
</head>
<body onload="initPage();">
<canvas id="canvas" width="610" height="610"></canvas>
</body>
</html>
android:
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.widget.ImageView;
public class MainActivity extends Activity {
private void drawToImageView(ImageView iv){
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.BLUE);
Path path = new Path();
int radius = 140;
int Radius = 3 * radius;
int padding = 5;
int width = 2 * (Radius + padding);
int height = width;
Bitmap bm = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bm);
float a = 90f;
float b = 3*a;
float pi = 2*a;
float _radius = (float)radius;
float _Radius = (float)Radius;
float centerX = padding + width/2;
float centerY = padding + height/2;
path.addArc(circRect(centerX, centerY, _Radius), a, pi);
path.addArc(circRect(centerX, centerY - 2*_radius, _radius), b, pi);
path.addArc(circRect(centerX, centerY, _radius), b, -pi);
path.addArc(circRect(centerX, centerY + 2*_radius, _radius), b, pi);
canvas.drawPath(path, paint);
iv.setImageDrawable(new BitmapDrawable(getResources(), bm));
}
private RectF circRect(float centerX, float centerY, float radius){
float left = centerX - radius;
float right = centerX + radius;
float top = centerY - radius;
float bottom = centerY + radius;
return new RectF(left, top, right, bottom);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ImageView iv = (ImageView)findViewById(R.id.myImageView);
drawToImageView(iv);
}
}