Converting a Circle To a Ring using Canvas? - android

I'm using a Custom View where I created a Circle:
canvas.drawCircle(width/2, width/2, radius, paint1);
Now, I want to empty the circle from inside, something like a ring.
Drawing another smaller white circle will raise another problem. I'm planning to make my view listen to user clicks. I want to listen to the clicks on the ring only!
Is there a way to exclude some part of the canvas?

In order to draw a ring (i.e., a circle that is NOT filled in but just has a border, with the area inside transparent) do this:
Paint myPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
myPaint.setStyle(Paint.Style.STROKE);
int strokeWidth=4; // or whatever
myPaint.setStrokeWidth(strokeWidth);
myPaint.setColor(0xffff0000); //color.RED
float radius=(float) (0.5*(w+h)*0.5);
canvas.drawCircle((float)0.5*w, (float)0.5*h, radius, myPaint);
The call to drawCircle can also be replaced by drawArc like the code below. Either approach will work. The secret sauce is to set the paint style to Paint.Style.STROKE.
RectF oval = new RectF(w/2 - radius, w/2 - radius, w/2 + radius, w/2 + radius);
canvas.drawArc(oval, 0, 360, false, myPaint);

To draw a ring, use drawArc :
RectF oval = new RectF(width/2 - radius, width/2 - radius, width/2 + radius, width/2 + radius);
canvas.drawArc(oval, 0, 360, false, paint1);

I worked around the problem by drawing an inner white circle and filtering the clicks based on the two radius lengths I had.
So, you can't draw an empty circle but you can make a circle to behave like an empty one.
Thanks.

this solution works:
Paint paintPath;
int circleRadius = (int)
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, context.getResources()
.getDisplayMetrics());
paintPath = new Paint();
paintPath.setStrokeWidth(circleRadius);
paintPath.setColor(Color.WHITE);
paintPath.setStyle(Paint.Style.FILL_AND_STROKE);
paintPath.setAntiAlias(true);
paintPath.setPathEffect(null);
paintPath.setRasterizer(null);
inside on draw
RectF rectF = new RectF(centerx - radius, centery - radius,
centerx + radius, centery + radius);
Path myPath = new Path();
for (int i = 0; i <= 360; i += 1)
{
myPath.addArc(rectF, i, 1);
}
canvas.drawPath(myPath, paintPath);

More short way:
draw = function() {
ctx.globalCompositeOperation = "lighter";
ctx.beginPath();
// ctx.fillStyle = "green";
ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2, false);
// ctx.fill();
ctx.strokeStyle = "yellow";
ctx.stroke();
ctx.closePath();
}

Related

Drawing a rounded hollow thumb over arc

I want to create a rounded graph that will display a range of values from my app. The values can be classified to 3 categories: low, mid, high - that are represented by 3 colors: blue, green and red (respectively).
Above this range, I want to show the actually measured values - in a form of a "thumb" over the relevant range part:
The location of the white thumb over the range arc may change, according to the measured values.
Currently, I'm able to draw the 3-colored range by drawing 3 arcs over the same center, inside the view's onDraw method:
width = (float) getWidth();
height = (float) getHeight();
float radius;
if (width > height) {
radius = height / 3;
} else {
radius = width / 3;
}
paint.setAntiAlias(true);
paint.setStrokeWidth(arcLineWidth);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStyle(Paint.Style.STROKE);
center_x = width / 2;
center_y = height / 1.6f;
left = center_x - radius;
float top = center_y - radius;
right = center_x + radius;
float bottom = center_y + radius;
oval.set(left, top, right, bottom);
//blue arc
paint.setColor(colorLow);
canvas.drawArc(oval, 135, 55, false, paint);
//red arc
paint.setColor(colorHigh);
canvas.drawArc(oval, 350, 55, false, paint);
//green arc
paint.setColor(colorNormal);
canvas.drawArc(oval, 190, 160, false, paint);
And this is the result arc:
My question is, how do I:
Create a smooth gradient between those 3 colors (I tried using
SweepGradient but it didn't give me the correct result).
Create the overlay white thumb as shown in the picture, so that I'll be able to control where to display it.
Animate this white thumb over my range arc.
Note: the 3-colored range is static - so another solution can be to just take the drawable and paint the white thumb over it (and animate it), so I'm open to hear such a solution as well :)
I would use masks for your first two problems.
1. Create a smooth gradient
The very first step would be drawing two rectangles with a linear gradient. The first
rectangle contains the colors blue and green while the second rectangle contains green
and red as seen in the following picture. I marked the line where both rectangles touch each other
black to clarify that they are infact two different rectangles.
This can be achieved using the following code (excerpt):
// Both color gradients
private Shader shader1 = new LinearGradient(0, 400, 0, 500, Color.rgb(59, 242, 174), Color.rgb(101, 172, 242), Shader.TileMode.CLAMP);
private Shader shader2 = new LinearGradient(0, 400, 0, 500, Color.rgb(59, 242, 174), Color.rgb(255, 31, 101), Shader.TileMode.CLAMP);
private Paint paint = new Paint();
// ...
#Override
protected void onDraw(Canvas canvas) {
float width = 800;
float height = 800;
float radius = width / 3;
// Arc Image
Bitmap.Config conf = Bitmap.Config.ARGB_8888; // See other config types
Bitmap mImage = Bitmap.createBitmap(800, 800, conf); // This creates a mutable bitmap
Canvas imageCanvas = new Canvas(mImage);
// Draw both rectangles
paint.setShader(shader1);
imageCanvas.drawRect(0, 0, 400, 800, paint);
paint.setShader(shader2);
imageCanvas.drawRect(400, 0, 800, 800, paint);
// /Arc Image
// Draw the rectangle image
canvas.save();
canvas.drawBitmap(mImage, 0, 0, null);
canvas.restore();
}
As your goal is having a colored arc with rounded caps, we next need to define the area of
both rectangles that should be visible to the user. This means that most of both rectangles
will be masked away and thus not visible. Instead the only thing to remain is the arc area.
The result should look like this:
In order to achieve the needed behavior we define a mask that only reveals the arc area within
the rectangles. For this we make heavy use of the setXfermode method of Paint. As argument
we use different instances of a PorterDuffXfermode.
private Paint maskPaint;
private Paint imagePaint;
// ...
// To be called within all constructors
private void init() {
// I encourage you to research what this does in detail for a better understanding
maskPaint = new Paint();
maskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
imagePaint = new Paint();
imagePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OVER));
}
#Override
protected void onDraw(Canvas canvas) {
// #step1
// Mask
Bitmap mMask = Bitmap.createBitmap(800, 800, conf);
Canvas maskCanvas = new Canvas(mMask);
paint.setColor(Color.WHITE);
paint.setShader(null);
paint.setStrokeWidth(70);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setAntiAlias(true);
final RectF oval = new RectF();
center_x = 400;
center_y = 400;
oval.set(center_x - radius,
center_y - radius,
center_x + radius,
center_y + radius);
maskCanvas.drawArc(oval, 135, 270, false, paint);
// /Mask
canvas.save();
// This is new compared to step 1
canvas.drawBitmap(mMask, 0, 0, maskPaint);
canvas.drawBitmap(mImage, 0, 0, imagePaint); // Notice the imagePaint instead of null
canvas.restore();
}
2. Create the overlay white thumb
This solves your first problem. The second one can be achieved using masks again, though this
time we want to achieve something different. Before, we wanted to show only a specific area (the arc)
of the background image (being the two rectangles). This time we want to do the opposite:
We define a background image (the thumb) and mask away its inner content, so that only
the stroke seems to remain. Applied to the arc image the thumb overlays the colored arc with
a transparent content area.
So the first step would be drawing the thumb. We use an arc for this with the same radius as
the background arc but different angles, resulting in a much smaller arc. But becaus the
thumb should "surround" the background arc, its stroke width has to be bigger than the
background arc.
#Override
protected void onDraw(Canvas canvas) {
// #step1
// #step2
// Thumb Image
mImage = Bitmap.createBitmap(800, 800, conf);
imageCanvas = new Canvas(mImage);
paint.setColor(Color.WHITE);
paint.setStrokeWidth(120);
final RectF oval2 = new RectF();
center_x = 400;
center_y = 400;
oval2.set(center_x - radius,
center_y - radius,
center_x + radius,
center_y + radius);
imageCanvas.drawArc(oval2, 270, 45, false, paint);
// /Thumb Image
canvas.save();
canvas.drawBitmap(RotateBitmap(mImage, 90f), 0, 0, null);
canvas.restore();
}
public static Bitmap RotateBitmap(Bitmap source, float angle)
{
Matrix matrix = new Matrix();
matrix.postRotate(angle);
return Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, true);
}
The result of the code is shown below.
So now that we have a thumb that is overlaying the background arc, we need to define the mask
that removes the inner part of the thumb, so that the background arc becomes visible again.
To achieve this we basically use the same parameters as before to create another arc, but
this time the stroke width has to be identical to the width used for the background arc as
this marks the area we want to remove inside the thumb.
Using the following code, the resulting image is shown in picture 4.
#Override
protected void onDraw(Canvas canvas) {
// #step1
// #step2
// Thumb Image
// ...
// /Thumb Image
// Thumb Mask
mMask = Bitmap.createBitmap(800, 800, conf);
maskCanvas = new Canvas(mMask);
paint.setColor(Color.WHITE);
paint.setStrokeWidth(70);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
final RectF oval3 = new RectF();
center_x = 400;
center_y = 400;
oval3.set(center_x - radius,
center_y - radius,
center_x + radius,
center_y + radius);
maskCanvas.drawBitmap(mImage, 0, 0, null);
maskCanvas.drawArc(oval3, 270, 45, false, paint);
// /Thumb Mask
canvas.save();
canvas.drawBitmap(RotateBitmap(mMask, 90f), 0, 0, null); // Notice mImage changed to mMask
canvas.restore();
}
3. Animate the white thumb
The last part of your question would be animating the movement of the arc. I have no solid
solution for this, but maybe can guide you in a useful direction. I would try the following:
First define the thumb as a ImageView that is part of your whole arc graph. When changing
the selected values of your graph, you rotate the thumb image around the center of the background
arc. Because we want to animate the movement, just setting the rotation of the thumb image would
not be adequate. Instead we use a RotateAnimation kind of like so:
final RotateAnimation animRotate = new RotateAnimation(0.0f, -90.0f, // You have to replace these values with your calculated angles
RotateAnimation.RELATIVE_TO_SELF, // This may be a tricky part. You probably have to change this to RELATIVE_TO_PARENT
0.5f, // x pivot
RotateAnimation.RELATIVE_TO_SELF,
0.5f); // y pivot
animRotate.setDuration(1500);
animRotate.setFillAfter(true);
animSet.addAnimation(animRotate);
thumbView.startAnimation(animSet);
This is far from final I guess, but it very well may aid you in your search for the needed
solution. It is very important that your pivot values have to refer to the center of your
background arc as this is the point your thumb image should rotate around.
I have tested my (full) code with API Level 16 and 22, 23, so I hope that this answer at least
gives you new ideas on how to solve your problems.
Please note that allocation operations within the onDraw method are a bad idea and should
be avoided. For simplicity I failed to follow this advise. Also the code is to be used as
a guide in the right direction and not to be simply copy & pasted, because it makes heavy
use of magic numbers and generally does not follow good coding standards.
I would change a bit of the way you draw your view, by looking on the original design, instead of drawing 3 caps I would draw just 1 line, that way the SweepGradient will work.
This migth be a bit tricky, you have 2 options:
create a Path with 4 arcs
draw 2 arcs- one is the big white (filled with white so you still want to use Paint.Style.STROKE) and another on top of that make it fill transparent, you can achieve it with PorterDuff xfermode, it probably take you couple of tries until you get that without clearing the green circle too.
I imagine you want to animate thumb position, so just use simple Animation that invalidate the view and draw the thumb view position accordingly.
Hopes this helps
Create a gradient than follow a path is not so simple.
So I can suggest you to use some libraries than already did it.
Include the library:
dependencies {
...
compile 'com.github.paroca72:sc-gauges:3.0.7'
}
Create the gauge in XML:
<com.sccomponents.gauges.library.ScArcGauge
android:id="#+id/gauge"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" />
Your code:
ScArcGauge gauge = this.findViewById(R.id.gauge);
gauge.setAngleSweep(270);
gauge.setAngleStart(135);
gauge.setHighValue(90);
int lineWidth = 50;
ScCopier baseLine = gauge.getBase();
baseLine.setWidths(lineWidth);
baseLine.setColors(Color.parseColor("#dddddd"));
baseLine.getPainter().setStrokeCap(Paint.Cap.ROUND);
ScCopier progressLine = gauge.getProgress();
progressLine.setWidths(lineWidth);
progressLine.setColors(
Color.parseColor("#65AAF2"),
Color.parseColor("#3EF2AD"),
Color.parseColor("#FF2465")
);
progressLine.getPainter().setStrokeCap(Paint.Cap.ROUND);
Your result:
You can find something more complex on this site:
ScComponents

Canvas fill color between two lines

I have two lines where one line is drawn at the same point every time and the other line is rotated by a certain degree every time the view is invalidated.
The code is as shown below :
protected void onDraw(Canvas canvas) {
int width = getWidth();
int height = getHeight();
int centerx = width/2;
int centery = height/2;
canvas.drawLine(centerx, 0, centerx, height, paint);
canvas.drawLine(0, centery, width, centery, paint);
if (angle_in_degrees!= null)
canvas.rotate(angle_in_degrees, centerx, centery);
paint.setColor(0xff0000ff);
canvas.drawLine(centerx, -1000, centerx, +1000, paint);
canvas.drawLine(-1000, centery, 1000, centery, paint);
paint.setColor(0xff00ff00);
}
Everything works fine, but I want to fill the space between the two lines by a different color. How do I do that? Thanks.
The accepted answer to this question sounds as if it would apply just as well to your situation. Essentially, the author uses a Path object to connect the dots between all points that need to be filled, before finally called canvas.drawPath() to fill the area inside.
In your case, this would probably look something like:
Path path = new Path();
path.lineTo(centerX, 0);
path.lineTo(centerX, height);
path.lineTo(0, centerY);
//etc. Essentially mirroring what your line does, but in one dimension (x,y) points.
canvas.drawPath(path, paint);
Edit: You would also need to make sure the Paint object you use has a FILL style:
paint.setStyle(Paint.Style.FILL);

How to create jigsaw puzzle pieces without using mask?

I am trying to create a jigsaw puzzle game, and I would like to know of alternative ways of creating puzzle pieces without using mask. Currently I have jigsaw pieces by taking a full image, breaking that image up into four pieces (lets say the puzzle is 2x2) and then storing and applying a mask to each piece. It looks like the below
// create standard puzzle pieces
arryPieceEndPos = new int[mCols][mRows];
arryPieceImg = new Bitmap[mCols * mRows];
arryIsPieceLocked = new boolean[mCols * mRows];
int pos = 0;
for (int c = 0; c < mCols; c++) {
for (int r = 0; r < mRows; r++) {
arryPieceImg[pos] = Bitmap.createBitmap(mBitmap,
c * mPieceWidth, r * mPieceHeight,
mPieceWidth, mPieceHeight);
arryIsPieceLocked[pos] = false;
arryPieceEndPos[c][r] = pos;
pos++;
}
}
I then use a helper method to apply a mask to each piece
private Bitmap maskMethod(Bitmap bmpOriginal, Bitmap bmpMask) {
// adjust mask bitmap if size is not the size of the puzzle piece
if (bmpMask.getHeight() != mPieceHeight ||
bmpMask.getWidth() != mPieceWidth) {
Log.e("TEST", "Resize Error :: H (mask): " + bmpMask.getHeight() + " // W (mask): " +
bmpMask.getWidth());
Log.d("TEST", "Resize Error :: H (norm): " + mPieceHeight + " // W (norm): " +
mPieceWidth);
}
Canvas canvas = new Canvas();
Bitmap combine = Bitmap.createBitmap(bmpOriginal.getWidth(), bmpOriginal.getHeight(), Bitmap.Config.ARGB_8888);
canvas.setBitmap(combine);
Paint paint = new Paint();
paint.setFilterBitmap(false);
canvas.drawBitmap(bmpOriginal, 0, 0, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
canvas.drawBitmap(bmpMask, 0, 0, paint);
paint.setXfermode(null);
return combine;
}
I saw this post > http://java.dzone.com/news/connect-pictures-android for connecting pieces together, however, this does not go over generating pieces programmatically without masks. Can anyone provide code examples of how this can be accomplished? The only clue I have is that I should be using Path, however, I am still not sure how. Thanks in advance!
A puzzle piece is a pretty complex view to create, but I can help you understand how to use path. Here is the link to the developer website: http://developer.android.com/reference/android/graphics/Path.html
Look into this link. I made a small thing for you to start. The one thing you need to figure out is how to cut a small circle out of the path, which I wouldn't know. I think you have to look into clipping to have your path follow a circle (you could also do clipping for creating the circle outside the piece, I just haven't done clipping before).
private Bitmap getPuzzleBitmap(Bitmap bitmap)
{
Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(output);
final int color = 0xff424242;
final Paint paint = new Paint();
final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
calculatePuzzlePath(bitmap.getWidth(), bitmap.getHeight());
paint.setAntiAlias(true);
canvas.drawARGB(0, 0, 0, 0);
paint.setColor(color);
canvas.drawPath(puzzlePath, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(bitmap, rect, rect, paint);
return output;
}
private void calculatePuzzlePath(int width, int height)
{
float radius = (height / 2) - 5;
float smallRadius = radius / 3;
radius -= smallRadius * 2;
float centerX = width/2;
float centerY = height/2;
puzzlePath = new Path();
// Bottom right
puzzlePath.moveTo(centerX + radius, centerY + radius);
// Top right
puzzlePath.lineTo(centerX + radius, centerY - radius);
// Center top
puzzlePath.lineTo(centerX, centerY - radius);
// Add outside circle to center top
puzzlePath.addCircle(centerX, centerY - radius - ((radius / 3) / 2), radius / 3, Path.Direction.CCW);
// Top left
puzzlePath.lineTo(centerX - radius, centerY - radius);
// Bottom left
puzzlePath.lineTo(centerX - radius, centerY + radius);
//Bottom right
puzzlePath.lineTo(centerX + radius, centerY + radius);
}
I hope this is sufficient to get started with this.
Good luck!

Draw a hollow circle on an Android Canvas

I am working on creating a custom view where a user can select an angle. Below is an example of what the end result should look like:
I am achieving this with the following code:
mPaint.setColor(Color.BLACK);
canvas.drawCircle((int) (mSize.right / 2), (int) (mSize.bottom / 2),
mWidthOutside, mPaint);
mPaint.setColor(Color.LTGRAY);
canvas.drawCircle((int) (mSize.right / 2), (int) (mSize.bottom / 2),
mWidthInside, mPaint);
The problem with doing it this way, is the background is a static LTGRAY, which I hope to make Transparent.
How would I go about leaving the center of the circle transparent?
I have tried the following hoping the the drawArc function would only create a line the width of the paint, and not fill the center. It does in face fill the center.
RectF rectF = new RectF(centerX - mRadius, centerY - mRadius, centerX
+ mRadius, centerY + mRadius);
canvas.drawArc(rectF, 0, 360, false, mPaint);
Suggestions on how to keep the center of the circle transparent?
As asked :)
The solution is to set the style to be Paint.Style.STROKE
Hollow Circle in canvas.
Bitmap bitmap=Bitmap.createBitmap(500,500, Bitmap.Config.ARGB_8888);
Canvas canvas=new Canvas(bitmap);
//canvas.clipPath(,Region.Op.DIFFERENCE);
Paint paint=new Paint();
paint.setColor(Color.RED);
paint.setStrokeWidth(140);
paint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(250,250,150,paint);
imageView.setImageBitmap(bitmap);

How to use android canvas to draw a Rectangle with only topleft and topright corners round?

I found a function for rectangles with all 4 corners being round, but I want to have just the top 2 corners round. What can I do?
canvas.drawRoundRect(new RectF(0, 100, 100, 300), 6, 6, paint);
For API 21 and above the Path class added a new method addRoundRect() which you can use it like this.
corners = new float[]{
80, 80, // Top left radius in px
80, 80, // Top right radius in px
0, 0, // Bottom right radius in px
0, 0 // Bottom left radius in px
};
final Path path = new Path();
path.addRoundRect(rect, corners, Path.Direction.CW);
canvas.drawPath(path, mPaint);
in Kotlin
val corners = floatArrayOf(
80f, 80f, // Top left radius in px
80f, 80f, // Top right radius in px
0f, 0f, // Bottom right radius in px
0f, 0f // Bottom left radius in px
)
val path = Path()
path.addRoundRect(rect, corners, Path.Direction.CW)
canvas.drawPath(path, mPaint)
Use a path. It has the advantage of working for APIs less than 21 (Arc is also limited thusly, which is why I quad). Which is a problem because not everybody has Lollipop yet. You can however specify a RectF and set the values with that and use arc back to API 1, but then you wouldn't get to use a static (without declaring a new object to build the object).
Drawing a rounded rect:
path.moveTo(right, top + ry);
path.rQuadTo(0, -ry, -rx, -ry);
path.rLineTo(-(width - (2 * rx)), 0);
path.rQuadTo(-rx, 0, -rx, ry);
path.rLineTo(0, (height - (2 * ry)));
path.rQuadTo(0, ry, rx, ry);
path.rLineTo((width - (2 * rx)), 0);
path.rQuadTo(rx, 0, rx, -ry);
path.rLineTo(0, -(height - (2 * ry)));
path.close();
As a full function:
static public Path RoundedRect(float left, float top, float right, float bottom, float rx, float ry, boolean conformToOriginalPost) {
Path path = new Path();
if (rx < 0) rx = 0;
if (ry < 0) ry = 0;
float width = right - left;
float height = bottom - top;
if (rx > width/2) rx = width/2;
if (ry > height/2) ry = height/2;
float widthMinusCorners = (width - (2 * rx));
float heightMinusCorners = (height - (2 * ry));
path.moveTo(right, top + ry);
path.rQuadTo(0, -ry, -rx, -ry);//top-right corner
path.rLineTo(-widthMinusCorners, 0);
path.rQuadTo(-rx, 0, -rx, ry); //top-left corner
path.rLineTo(0, heightMinusCorners);
if (conformToOriginalPost) {
path.rLineTo(0, ry);
path.rLineTo(width, 0);
path.rLineTo(0, -ry);
}
else {
path.rQuadTo(0, ry, rx, ry);//bottom-left corner
path.rLineTo(widthMinusCorners, 0);
path.rQuadTo(rx, 0, rx, -ry); //bottom-right corner
}
path.rLineTo(0, -heightMinusCorners);
path.close();//Given close, last lineto can be removed.
return path;
}
You'd want to line all the way to those corner bits, rather than quad across them. This is what setting true to conformToOriginalPost does. Just line to the control point there.
If you want to do that all but don't care about pre-Lollipop stuff, and urgently insist that if your rx and ry are high enough, it should draw a circle.
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
static public Path RoundedRect(float left, float top, float right, float bottom, float rx, float ry, boolean conformToOriginalPost) {
Path path = new Path();
if (rx < 0) rx = 0;
if (ry < 0) ry = 0;
float width = right - left;
float height = bottom - top;
if (rx > width/2) rx = width/2;
if (ry > height/2) ry = height/2;
float widthMinusCorners = (width - (2 * rx));
float heightMinusCorners = (height - (2 * ry));
path.moveTo(right, top + ry);
path.arcTo(right - 2*rx, top, right, top + 2*ry, 0, -90, false); //top-right-corner
path.rLineTo(-widthMinusCorners, 0);
path.arcTo(left, top, left + 2*rx, top + 2*ry, 270, -90, false);//top-left corner.
path.rLineTo(0, heightMinusCorners);
if (conformToOriginalPost) {
path.rLineTo(0, ry);
path.rLineTo(width, 0);
path.rLineTo(0, -ry);
}
else {
path.arcTo(left, bottom - 2 * ry, left + 2 * rx, bottom, 180, -90, false); //bottom-left corner
path.rLineTo(widthMinusCorners, 0);
path.arcTo(right - 2 * rx, bottom - 2 * ry, right, bottom, 90, -90, false); //bottom-right corner
}
path.rLineTo(0, -heightMinusCorners);
path.close();//Given close, last lineto can be removed.
return path;
}
So,
conformToOriginalPost actually draws a rounded rect without the bottom two bits rounded.
I would draw two rectangles:
canvas.drawRect(new RectF(0, 110, 100, 290), paint);
canvas.drawRoundRect(new RectF(0, 100, 100, 200), 6, 6, paint);
Or something like that, you just overlap them so that the upper corners will be round. Preferably you should write a method for this
I changed this answer so you can set which corner you want to be round and which one you want to be sharp. also works on pre-lolipop
Usage Example:
only top-right and botton-right corners are rounded
Path path = RoundedRect(0, 0, fwidth , fheight , 5,5,
false, true, true, false);
canvas.drawPath(path,myPaint);
RoundRect:
public static Path RoundedRect(
float left, float top, float right, float bottom, float rx, float ry,
boolean tl, boolean tr, boolean br, boolean bl
){
Path path = new Path();
if (rx < 0) rx = 0;
if (ry < 0) ry = 0;
float width = right - left;
float height = bottom - top;
if (rx > width / 2) rx = width / 2;
if (ry > height / 2) ry = height / 2;
float widthMinusCorners = (width - (2 * rx));
float heightMinusCorners = (height - (2 * ry));
path.moveTo(right, top + ry);
if (tr)
path.rQuadTo(0, -ry, -rx, -ry);//top-right corner
else{
path.rLineTo(0, -ry);
path.rLineTo(-rx,0);
}
path.rLineTo(-widthMinusCorners, 0);
if (tl)
path.rQuadTo(-rx, 0, -rx, ry); //top-left corner
else{
path.rLineTo(-rx, 0);
path.rLineTo(0,ry);
}
path.rLineTo(0, heightMinusCorners);
if (bl)
path.rQuadTo(0, ry, rx, ry);//bottom-left corner
else{
path.rLineTo(0, ry);
path.rLineTo(rx,0);
}
path.rLineTo(widthMinusCorners, 0);
if (br)
path.rQuadTo(rx, 0, rx, -ry); //bottom-right corner
else{
path.rLineTo(rx,0);
path.rLineTo(0, -ry);
}
path.rLineTo(0, -heightMinusCorners);
path.close();//Given close, last lineto can be removed.
return path;
}
you can easily achieve this by using Path:
val radiusArr = floatArrayOf(
15f, 15f,
15f, 15f,
0f, 0f,
0f, 0f
)
val myPath = Path()
myPath.addRoundRect(
RectF(0f, 0f, 400f, 400f),
radiusArr,
Path.Direction.CW
)
canvas.drawPath(myPath, paint)
Simple helper function written in Kotlin.
private fun Canvas.drawTopRoundRect(rect: RectF, paint: Paint, radius: Float) {
// Step 1. Draw rect with rounded corners.
drawRoundRect(rect, radius, radius, paint)
// Step 2. Draw simple rect with reduced height,
// so it wont cover top rounded corners.
drawRect(
rect.left,
rect.top + radius,
rect.right,
rect.bottom,
paint
)
}
Usage:
canvas.drawTopRoundRect(rect, paint, radius)
public static Path composeRoundedRectPath(RectF rect, float topLeftDiameter, float topRightDiameter,float bottomRightDiameter, float bottomLeftDiameter){
Path path = new Path();
topLeftDiameter = topLeftDiameter < 0 ? 0 : topLeftDiameter;
topRightDiameter = topRightDiameter < 0 ? 0 : topRightDiameter;
bottomLeftDiameter = bottomLeftDiameter < 0 ? 0 : bottomLeftDiameter;
bottomRightDiameter = bottomRightDiameter < 0 ? 0 : bottomRightDiameter;
path.moveTo(rect.left + topLeftDiameter/2 ,rect.top);
path.lineTo(rect.right - topRightDiameter/2,rect.top);
path.quadTo(rect.right, rect.top, rect.right, rect.top + topRightDiameter/2);
path.lineTo(rect.right ,rect.bottom - bottomRightDiameter/2);
path.quadTo(rect.right ,rect.bottom, rect.right - bottomRightDiameter/2, rect.bottom);
path.lineTo(rect.left + bottomLeftDiameter/2,rect.bottom);
path.quadTo(rect.left,rect.bottom,rect.left, rect.bottom - bottomLeftDiameter/2);
path.lineTo(rect.left,rect.top + topLeftDiameter/2);
path.quadTo(rect.left,rect.top, rect.left + topLeftDiameter/2, rect.top);
path.close();
return path;
}
I achieved this by following the below steps.
These are the pre-requisites for the rounded rectangle to look neat
The radius of the edges have to be equal to the (height of the rectangle / 2). This is because if its any different value then the place where the curve meets straight line of the rectangle will not be
Next is the steps to draw the rounded rectangle.
First we draw 2 circles on the left and right side, with the radius = height of rectange / 2
Then we draw a rectangle between these circles to get the desired rounded rectangle.
I am posting the code below
private void drawRoundedRect(Canvas canvas, float left, float top, float right, float bottom) {
float radius = getHeight() / 2;
canvas.drawCircle(radius, radius, radius, mainPaint);
canvas.drawCircle(right - radius, radius, radius, mainPaint);
canvas.drawRect(left + radius, top, right - radius, bottom, mainPaint);
}
Now this results in a really nice rounded rectangle like the one shown below
This is an old question, however I wanted to add my solution because it uses the native SDK without lots of custom code or hacky drawing. This solution is supported back to API 1.
The way to do this properly is to create a path (as mentioned in other answers) however the previous answers seem to overlook the addRoundedRect function call that takes radii for each corner.
Variables
private val path = Path()
private val paint = Paint()
Setup Paint
paint.color = Color.RED
paint.style = Paint.Style.FILL
Update Path with Size Changes
Call this somewhere that isn't onDraw, such as onMeasure for a view or onBoundChange for a drawable. If it doesn't change (like this example) you could put this code where you set up your paint.
val radii = floatArrayOf(
25f, 25f, //Top left corner
25f, 25f, //Top right corner
0f, 0f, //Bottom right corner
0f, 0f, //Bottom left corner
)
path.reset() //Clears the previously set path
path.addRoundedRect(0f, 0f, 100f, 100f, radii, Path.Direction.CW)
This code creates a 100x100 rounded rect with the top corners rounded with a 25 radius.
Draw Path
Call this in onDraw for a view or draw for a drawable.
canvas.drawPath(path, paint)
A Path#arcTo() version to draw the rounded side if the radius is half of the height.
fun getPathOfRoundedRectF(
rect: RectF,
topLeftRadius: Float = 0f,
topRightRadius: Float = 0f,
bottomRightRadius: Float = 0f,
bottomLeftRadius: Float = 0f
): Path {
val tlRadius = topLeftRadius.coerceAtLeast(0f)
val trRadius = topRightRadius.coerceAtLeast(0f)
val brRadius = bottomRightRadius.coerceAtLeast(0f)
val blRadius = bottomLeftRadius.coerceAtLeast(0f)
with(Path()) {
moveTo(rect.left + tlRadius, rect.top)
//setup top border
lineTo(rect.right - trRadius, rect.top)
//setup top-right corner
arcTo(
RectF(
rect.right - trRadius * 2f,
rect.top,
rect.right,
rect.top + trRadius * 2f
), -90f, 90f
)
//setup right border
lineTo(rect.right, rect.bottom - trRadius)
//setup bottom-right corner
arcTo(
RectF(
rect.right - brRadius * 2f,
rect.bottom - brRadius * 2f,
rect.right,
rect.bottom
), 0f, 90f
)
//setup bottom border
lineTo(rect.left + blRadius, rect.bottom)
//setup bottom-left corner
arcTo(
RectF(
rect.left,
rect.bottom - blRadius * 2f,
rect.left + blRadius * 2f,
rect.bottom
), 90f, 90f
)
//setup left border
lineTo(rect.left, rect.top + tlRadius)
//setup top-left corner
arcTo(
RectF(
rect.left,
rect.top,
rect.left + tlRadius * 2f,
rect.top + tlRadius * 2f
),
180f,
90f
)
close()
return this
}
}
One simple and efficient way to draw a solid side is to use clipping - rect clipping is essentially free, and a lot less code to write than a custom Path.
If I want a 300x300 rect, with the top left and right rounded by 50 pixels, you can do:
canvas.save();
canvas.clipRect(0, 0, 300, 300);
canvas.drawRoundRect(new RectF(0, 0, 300, 350), 50, 50, paint);
canvas.restore();
This approach will only work for rounding on 2 or 3 adjacent corners, so it's a little less configurable than a Path based approach, but using round rects is more efficient, since drawRoundRect() is fully hardware accelerated (that is, tessellated into triangles) while drawPath() always falls back to software rendering (software-draw a path bitmap, and upload that to be cached on the GPU).
Not a huge performance issue for small infrequent drawing, but if you're animating paths, the cost of software draw can make your frame times longer, and increase your chance to drop frames. The path mask also costs memory.
If you do want to go with a Path-based approach, I'd recommend using GradientDrawable to simplify the lines of code (assuming you don't need to set a custom shader, e.g. to draw a Bitmap).
mGradient.setBounds(0, 0, 300, 300);
mGradient.setCornerRadii(new int[] {50,50, 50,50, 0,0, 0,0});
With GradientDrawable#setCornerRadii(), you can set any corner to be any roundedness, and reasonably animate between states.
Here is my answer to the above question. Here, I have created Kotlin extension function which uses Path along with the quadTo function which can be used in lower-level APIs also.
fun Canvas.drawRoundRectPath(
rectF: RectF,
radius: Float,
roundTopLeft: Boolean,
roundTopRight: Boolean,
roundBottomLeft: Boolean,
roundBottomRight: Boolean,
paint: Paint) {
val path = Path()
//Move path cursor to start point
if (roundBottomLeft) {
path.moveTo(rectF.left, rectF.bottom - radius)
} else {
path.moveTo(rectF.left, rectF.bottom)
}
// drawing line and rounding top left curve
if (roundTopLeft) {
path.lineTo(rectF.left, rectF.top + radius)
path.quadTo(rectF.left, rectF.top, rectF.left + radius, rectF.top)
} else {
path.lineTo(rectF.left, rectF.top)
}
// drawing line an rounding top right curve
if (roundTopRight) {
path.lineTo(rectF.right - radius, rectF.top)
path.quadTo(rectF.right, rectF.top, rectF.right, rectF.top + radius)
} else {
path.lineTo(rectF.right, rectF.top)
}
// drawing line an rounding bottom right curve
if (roundBottomRight) {
path.lineTo(rectF.right, rectF.bottom - radius)
path.quadTo(rectF.right, rectF.bottom, rectF.right - radius, rectF.bottom)
} else {
path.lineTo(rectF.right, rectF.bottom)
}
// drawing line an rounding bottom left curve
if (roundBottomLeft) {
path.lineTo(rectF.left + radius, rectF.bottom)
path.quadTo(rectF.left, rectF.bottom, rectF.left, rectF.bottom - radius)
} else {
path.lineTo(rectF.left, rectF.bottom)
}
path.close()
drawPath(path, paint)
}
We can call the function with canvas object and pass the RectF with the dimension on which we want to apply the curve.
Also, we can pass the boolean for the corners which we want to round.
This answer can further be customized to accept radius for individual corners.
You can draw that piece by piece using drawLine() and drawArc() functions from the Canvas.
Maybe the following code can help you
Paint p = new Paint();
p.setColor(color);
float[] corners = new float[]{
15, 15, // Top, left in px
15, 15, // Top, right in px
15, 15, // Bottom, right in px
15, 15 // Bottom,left in px
};
final Path path = new Path();
path.addRoundRect(rect, corners, Path.Direction.CW);
// Draw
canvas.drawPath(path, p);
Use PaintDrawable could be better:
val topLeftRadius = 10
val topRightRadius = 10
val bottomLeftRadius = 0
val bottomRightRadius = 0
val rect = Rect(0, 0, 100, 100)
val paintDrawable = PaintDrawable(Color.RED)
val outter = floatArrayOf(topLeftRadius, topLeftRadius, topRightRadius, topRightRadius,
bottomLeftRadius, bottomLeftRadius, bottomRightRadius, bottomRightRadius)
paintDrawable.setCornerRadii(outter)
paintDrawable.bounds = rect
paintDrawable.draw(canvas)
draw round rect with left rounded corners
private void drawRoundRect(float left, float top, float right, float bottom, Paint paint, Canvas canvas) {
Path path = new Path();
path.moveTo(left, top);
path.lineTo(right, top);
path.lineTo(right, bottom);
path.lineTo(left + radius, bottom);
path.quadTo(left, bottom, left, bottom - radius);
path.lineTo(left, top + radius);
path.quadTo(left, top, left + radius, top);
canvas.drawPath(path, onlinePaint);
}

Categories

Resources