How to rotate text drawn using canvas.drawTextOnPath()? - android

I am using drawTextOnPath() to create a bent text like in given picture below but I can't figure out how to rotate the text?
What I want is below highlighted in picture with arrow and circle.
What i have been able to achieve is below
So I just want to rotate these texts?

You can achieve the result you want without drawTextOnPath() using:
float radius = 300;
float cx = getWidth() / 2; //Center of the circle
float cy = getHeight() / 2; //Center of the circle
for (int degree = 0; degree < 360; degree += 30) {
canvas.save();
canvas.translate(cx, cy);
canvas.rotate(degree);
String text = "" + degree;
float textCenterX = radius + textPaint.measureText(text) / 2;
float textCenterY = 0 - textPaint.getTextSize() / 2;
if (degree < 180) {
canvas.rotate(-90, textCenterX, textCenterY);
} else {
canvas.rotate(+90, textCenterX, textCenterY);
}
canvas.drawText("" + degree, radius, 0, textPaint);
canvas.restore();
}

You just need to flip the canvas and draw on it
canvas.save()
canvas.scale(1, -1, width / 2, height / 2)
// your drawing code
canvas.restore()

Related

android canvas animate background?

I'm trying to animate a background in a game I'm making.
https://imgur.com/kQGWsLu
Should look like this(terrible recording quality)
My first attempt work well on my phone but a lot of people saw their fps cut in half:
public void draw(Canvas canvas, Paint paint) {
if (mPaint == null) {
mPaint = new Paint();
mPaint.setColor(Color.GRAY);
mPaint.setStrokeWidth(1);
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setShader(new RadialGradient(canvas.getWidth() / 4, canvas.getHeight() / 3,
Math.max(1, canvas.getHeight() / 2), Color.rgb(5, 12, 127), Color.rgb(32, 36, 100), Shader.TileMode.REPEAT));
}
width = canvas.getWidth();
height = canvas.getHeight();
canvas.save();
rotation += 4;
if (rotation > 360)
rotation = 0;
canvas.rotate(rotation, width / 2, height / 2);
canvas.drawCircle(width / 2, height / 2, height * 1.3f, mPaint);
canvas.restore();
}
So now I'm thinking about of pre rendering a image and drawing it as a bitmap that covers the screen and rotates instead. I also cut the calculations to only update every other frame instead.
public void draw(Canvas canvas, Paint paint) {
if (!initiated) {
centerX = (int) (canvas.getWidth() / 2);
centerY = (int) (canvas.getHeight() / 2);
width = (int) (canvas.getWidth() * 2f);
height = (int) (canvas.getHeight() * 2f);
map = factory.GFX().getBackground();
initiated = true;
}
if (!skipUpdate) {
x = CalculatorService.getXCircle(rotation, distance, centerX);
y = CalculatorService.getYCircle(rotation, distance, centerY);
body.set((int) x - width / 2,
(int) y - height / 2,
(int) x + (width / 2),
(int) y + (height / 2));
rotation += rotationSpeed;
if (rotation > 360)
rotation = 0;
}
skipUpdate = !skipUpdate;
canvas.drawBitmap(map, null, body, paint);
}
Though since the image is small in size 320*320 I had to scale it up and draw it bigger then the screen to avoid it moving out of bounds and showing the basic black background, and I'm not sure if this is really a improvement, do anyone have a better way of doing it or any advice on how I can improve?

Custom Drawable border overlaps with App Bar Layout Header

This is how my custom drawable looks by default.
But when scrolled, it overlaps with the AppBarLayout.
The code for the Drawable goes like this:
#Override
public void draw(#NonNull Canvas canvas) {
// get drawable dimensions
Rect bounds = getBounds();
float width = bounds.right - bounds.left;
float height = bounds.bottom - bounds.top;
float w2 = width / 2;
float h2 = height / 2;
float radius = Math.min(w2, h2) - mStrokeWidth / 2;
mPath.reset();
mPath.addCircle(width / 2, height / 2, radius, Path.Direction.CW);
canvas.clipPath(mPath);
// draw background gradient
float barHeight = height / themeColors.length;
mRectF.left = 0;
mRectF.top = 0;
mRectF.right = width;
mRectF.bottom = height;
for (int i = 0; i < themeColors.length; i++) {
mPaint.setColor(themeColors[i]);
canvas.drawRect(0, i * barHeight, width, (i + 1) * barHeight, mPaint);
}
mRectF.set(0, 0, width, height);
canvas.clipRect(mRectF, Region.Op.REPLACE);
if (mStrokeWidth != 0)
canvas.drawCircle(width / 2, height / 2, width / 2 - mStrokeWidth / 2, mStrokePaint);
}
Support Library Version: 25.3.1, 26.1.0
What I've tried:
- Different Region values for clipping path instead of REPLACE
- Clipping path rectangle first and then clipping circle.
How do I fix this ?
I am posting my solution as answer.
The reason it was getting overlapped was because the canvas was getting clipped twice without saving it.
I removed this statement:
canvas.clipRect(mRectF, Region.Op.REPLACE);
and before clipping the canvas for the first time
i saved its state using
canvas.save();
canvas.clipPath(mPath);
and then when i was drawing the stroke, i need the original canvas so i restored it
canvas.restore();
if (mStrokeWidth != 0)
canvas.drawCircle(width / 2, height / 2, width / 2 - mStrokeWidth / 2, mStrokePaint);
This fixed the issue.
Final Drawable Code:
#Override
public void draw(#NonNull Canvas canvas) {
// get drawable dimensions
Rect bounds = getBounds();
float width = bounds.right - bounds.left;
float height = bounds.bottom - bounds.top;
float w2 = width / 2;
float h2 = height / 2;
float radius = Math.min(w2, h2) - mStrokeWidth / 2;
mPath.reset();
mPath.addCircle(width / 2, height / 2, radius, Path.Direction.CW);
canvas.save();
canvas.clipPath(mPath);
// draw background gradient
float barHeight = height / themeColors.length;
mRectF.left = 0;
mRectF.top = 0;
mRectF.right = width;
mRectF.bottom = height;
for (int i = 0; i < themeColors.length; i++) {
mPaint.setColor(themeColors[i]);
canvas.drawRect(0, i * barHeight, width, (i + 1) * barHeight, mPaint);
}
mRectF.set(0, 0, width, height);
//canvas.clipRect(mRectF, Region.Op.REPLACE);
canvas.restore();
if (mStrokeWidth != 0)
canvas.drawCircle(width / 2, height / 2, width / 2 - mStrokeWidth / 2, mStrokePaint);
}

Drawing on an Android Canvas, angle is not correct

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.

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

Categories

Resources