77

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);
peer
  • 4,171
  • 8
  • 42
  • 73
virsir
  • 15,159
  • 25
  • 75
  • 109
  • 1
    Can't you just draw one rounded rectangle and then an ordinary rectangle across the bottom half (overwriting the rounded corners with normal ones?) – interstar Feb 05 '21 at 19:59

16 Answers16

68

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)
laszlo
  • 494
  • 4
  • 18
Saran Sankaran
  • 2,335
  • 2
  • 19
  • 34
64

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.

arcquadimage

Tatarize
  • 10,238
  • 4
  • 58
  • 64
  • In my opinion I believe relative path functions are much better because you only need to worry about the one point the path is going to be drawn at. – TheRealChx101 Oct 21 '15 at 09:59
  • @chx101 yeah, okay, redid it as relative and started on a curve so the close() could cover the lineto. – Tatarize Oct 22 '15 at 20:08
  • @Tatrize Wow, you re-wrote the whole thing huh? – TheRealChx101 Oct 23 '15 at 04:25
  • There's parts of the original answer in there. I did basically end up mostly scrapping the original code, and actually fully provide the actual requested answer. But, it was more because the arcs were being rude, and so I had to reverse the direction, and then reordered them to put the last two at the bottom to let them be easily removed. Then I checked the W3 Rect for the typical standard implementation of what to do for various RX, RY values (opted for 0 if negative rather than throw an error). http://www.w3.org/TR/SVG/shapes.html#RectElement – Tatarize Oct 23 '15 at 05:18
  • 1
    And it occurs to me that even without a circle arc, one could do a much more circle-ish curve with a quadratic bezier rather than a quad. Namely you'd do control points at 1,c and c,1 (with respect to the quadrant) where C = (4/3)*tan(pi/8) or do the spencer mortensen slightly improved value for C. A quad with the control point at the corner works, but it's actually a bit naive. – Tatarize Jan 03 '16 at 08:31
  • Yeah, certainly right.. At least one of those words needs to be cubic. "with a quad bezier rather than a quad" and while there's certainly a way to do it with a quad and *still* be more arc-like (though I've never seen an attempt) the best cubic solution equalizes the error like: http://spencermortensen.com/articles/bezier-circle/ – Tatarize Aug 01 '16 at 18:25
  • I bothered to solve it the answer turns out to be c = 2*cos(pi/4)-1/2 or 0.91421356237. So rather than quad to the corner as I do here if we quad to the point (0.91421356237 rx ,0.91421356237 ry) rather than (1 rx ,1 ry) we would get the best of both worlds. Nearly minimized error on the curve and only using a quad. – Tatarize Aug 01 '16 at 21:16
  • So for the Quadrant I our curve would be q(0,1, c, 1 - c,1,0) where c = 2*cos(pi/4)-1/2 or 0.91421356237 – Tatarize Aug 01 '16 at 21:24
  • When i want to draw a circle with Quad, then it should not draw's a pure circle [see this](https://i.stack.imgur.com/lThKk.png), so i will using arc, Thanks for this methods. – S Kumar May 13 '20 at 21:02
  • @SKumar that image clearly has the quad going to the corner fully and pulls out too far if you go out at only .91% of the way there you get a much more circular path. But, yeah, use arcs if you want a real circle. But, the error goes down to a few percent if you fake it correctly. – Tatarize May 15 '20 at 07:32
55

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

Durza007
  • 1,095
  • 10
  • 9
  • 13
    What about I need to set alpha with the whole rect, the overlap section would have different alpha value with others – virsir May 05 '11 at 12:13
  • 4
    @V.Kalyuzhnyu the method signature used in the answer is available since API 1, you must be talking about `drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, Paint paint)` which is available from API 21. – Mokkun May 01 '17 at 08:26
  • @virsir `paint.setColor(Color.argb(200, 0,0,0));` will set alpha to the paint. – niek tuytel Nov 28 '20 at 20:24
31

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;
    }
Community
  • 1
  • 1
Moh Mah
  • 2,005
  • 20
  • 29
  • 1
    You should fix my my answer there with proper bezier arc approximating corners. http://spencermortensen.com/articles/bezier-circle/ And convert use a C-style style bitwise thing for the corners rather than a bunch of booleans. static final int SHARP_TOP_RIGHT = 0b0001, static final int SHARP_TOP_LEFT = 0b0010; -- then you can call it, getRoundRect(0, 0, fwidth , fheight , 5, 5, SHARP_TOP_LEFT | SHARP_BOTTOM_LEFT); with the ifs being (if ((SHARP_TOP_LEFT & cornerflag) == 0) ... ; also a helper function that if omitted calls it with 0. So RoundRect(l,tl,r,b,rx,ry) works fine. All static final. – Tatarize Apr 17 '16 at 07:39
  • 1
    Perfect! That was what I was looking for.! Thanks buddy! – Adnan Jun 09 '16 at 06:07
  • 1
    I can't thank you enough! This is perfect! You just saved me a lot of hours! THANKS! – Nick Feb 29 '20 at 09:34
  • Thank you very much, I use this before blur the view like this: @Override protected void onDraw(Canvas canvas) { Path clipPath = RoundedRect(0, 0, getWidth(), getHeight(), mCornerRadius, mCornerRadius, mTopLeftCorner, mTopRightCorner, mBottomRightCorner, mBottomLeftCorner); canvas.clipPath(clipPath); super.onDraw(canvas); drawBlurredBitmap(canvas, mBlurredBitmap, mOverlayColor); } – Ho Luong Aug 13 '20 at 09:26
13

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)
Hashem Mousavi
  • 366
  • 4
  • 7
11

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)
Vadim Ahmerov
  • 708
  • 9
  • 12
10
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;
}
Yan
  • 1,569
  • 18
  • 22
  • 2
    Thanks for the code, it worked well for me. But there is a little error in the code: You're working with half of the required radius -- I suppose you take the radius as the diameter, but radius is already half the diameter, so you should drop all the /2 in the code. – Ridcully Dec 10 '16 at 17:41
7

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 belowenter image description here

Bhargav
  • 8,118
  • 6
  • 40
  • 63
7

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)
Cassie
  • 5,223
  • 3
  • 22
  • 34
7

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

Two rounded corners Three rounded corners Three rounded corners

firemaples
  • 1,521
  • 13
  • 18
5

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.

Chris Craik
  • 184
  • 1
  • 3
4

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.

Ashish M
  • 763
  • 6
  • 13
3

You can draw that piece by piece using drawLine() and drawArc() functions from the Canvas.

Zelimir
  • 11,008
  • 6
  • 50
  • 45
3

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);
James Ryan
  • 61
  • 1
2

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)
GreatC
  • 342
  • 4
  • 9
1

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);
    }
georgehardcore
  • 975
  • 14
  • 12
  • This method does not provide rounded corners for a rectangle, it provides rounded shapes for some corners but not for all, the result shape is distorted. You must work on that a little bit I guess. – Bahadir Tasdemir Sep 02 '15 at 06:43
  • 1
    you should not create new objects at onDraw method - use preallocated Path – Evgenii Vorobei Apr 20 '19 at 16:14