如何使用 Android 画布绘制一个仅具有左上和右上圆角的矩形?

77

我找到了一个可以制作所有四个角都是圆形的矩形的函数,但我想只让顶部两个角是圆形的。我该怎么做?

canvas.drawRoundRect(new RectF(0, 100, 100, 300), 6, 6, paint);

1
你不能只画一个圆角矩形,然后在底部半部分画一个普通矩形(用普通的矩形覆盖圆角部分)吗? - interstar
16个回答

70

从API 21开始,Path类添加了一个新方法addRoundRect(),您可以像这样使用它。

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);

在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)

9
这就是我一直在寻找的圣杯!谢谢,伙计! - Starwave
正确的解决方案 - metodieva
好的答案,我因路径填充类型而感到烦恼,这解决了我的问题。 - Thomas Cook

65

使用路径(path)有一个优点,可以适用于低于21的API(同样,Arc也受到限制,这就是为什么我要四边形)。这是一个问题,因为不是每个人都拥有Lollipop。但是您可以指定一个RectF并设置其值,然后在API 1上使用弧形(arc),但是这样您将无法使用静态对象(不声明新对象以构建该对象)。

绘制圆角矩形:

    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();

作为一个全功能:

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

你需要将线条延伸到那些角的边缘,而不是穿过它们。这就是将conformToOriginalPost设置为true所做的事情。只需将线条连接到控制点即可。

如果你想要做到这一点,但不关心Lollipop之前的内容,并且迫切地坚持认为如果你的Rx和Ry足够高,它应该画一个圆。

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

因此,conformToOriginalPost实际上绘制了一个除了底部两个位以外都是圆角矩形。

arcquadimage


在我看来,相对路径函数更好,因为你只需要关心路径将被绘制的一个点。 - TheRealChx101
@chx101 好的,我把它重做成相对路径,并从曲线开始,这样close()就可以覆盖lineto了。 - Tatarize
其中有原始答案的部分。我基本上最终大部分放弃了原始代码,并提供了实际请求的答案。但是,这更多是因为弧线很粗鲁,所以我不得不反转方向,然后重新排序它们,将最后两个放在底部,以便轻松删除它们。然后,我检查了W3 Rect,以获取各种RX、RY值的典型标准实现(如果为负,则选择0而不是抛出错误)。http://www.w3.org/TR/SVG/shapes.html#RectElement - Tatarize
1
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Tatarize
因此,对于第一象限,我们的曲线将是q(0,1,c,1-c,1,0),其中c = 2*cos(pi/4)-1/2或0.91421356237。 - Tatarize
显示剩余5条评论

55

我会画两个矩形:

canvas.drawRect(new RectF(0, 110, 100, 290), paint);
canvas.drawRoundRect(new RectF(0, 100, 100, 200), 6, 6, paint);

或者类似于这样,只需重叠它们,以便上角会变成圆形。最好编写一个方法来实现此功能。


13
如果我需要对整个矩形设置alpha值,那么重叠部分的alpha值将与其他部分不同。 - virsir
4
答案中使用的方法签名从API 1就已经存在了,你可能在谈论的是drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, Paint paint)这个方法,它从API 21开始提供。 - Mokkun
@virsir paint.setColor(Color.argb(200, 0,0,0)); 将设置绘画的 alpha 值。 - niek tuytel

31

我修改了这个答案,所以您可以设置哪个角要圆,哪个角要尖,也适用于早期的Lollipop版本。

使用示例

只有右上角和右下角是圆的

 Path path = RoundedRect(0, 0, fwidth , fheight , 5,5,
                     false, true, true, false);
 canvas.drawPath(path,myPaint);

圆角矩形:

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

1
你应该使用适当的贝塞尔弧来近似角度,修复我的答案。http://spencermortensen.com/articles/bezier-circle/并且使用C风格的位运算符来代替一堆布尔值。static final int SHARP_TOP_RIGHT = 0b0001, static final int SHARP_TOP_LEFT = 0b0010; -- 然后你可以调用它,getRoundRect(0, 0, fwidth , fheight , 5, 5, SHARP_TOP_LEFT | SHARP_BOTTOM_LEFT); ifs语句为(if ((SHARP_TOP_LEFT & cornerflag) == 0) ... ; 还有一个帮助函数,如果省略了则调用它并将其设置为0。所以RoundRect(l,tl,r,b,rx,ry)很好用。全部都是静态常量。 - Tatarize
1
完美!这就是我一直在寻找的!谢谢,伙计! - Adnan
1
我无法感谢你们的足够!这太完美了!你刚刚为我节省了很多时间!谢谢! - Nick
非常感谢,我之前使用这个来模糊视图,像这样:@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

13

你可以通过使用路径(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)

11

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

使用方法:

canvas.drawTopRoundRect(rect, paint, radius)

6
它们将重叠,颜色透明度会不同。 - Jemshit Iskenderov
drawRoundRect() 需要 API 级别 21。 - Swathi

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

2
谢谢你提供的代码,它对我很有帮助。但是代码中有一个小错误:你使用了半径的一半--我猜你把直径当作了半径,但是半径已经是直径的一半了,所以你应该在代码中去掉所有的/2。 - Ridcully

7
我通过以下步骤实现了这一点。
以下是圆角矩形看起来整洁的先决条件:
- 边缘半径必须等于(矩形高度/2)。这是因为如果值不同,则曲线与矩形直线相交的地方将不正确。
接下来是绘制圆角矩形的步骤。
- 首先,在左侧和右侧绘制两个半径为矩形高度/2的圆。 - 然后,在这些圆之间绘制一个矩形,以获得所需的圆角矩形。
我在下面发布代码。
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);
}

现在,这将产生一个非常漂亮的圆角矩形,就像下面展示的那个一样enter image description here

7

这是一个老问题,不过我想分享我的解决方案,因为它使用了原生SDK而没有大量的自定义代码或hacky绘图。此解决方案支持API 1及以上版本。

正确的方法是创建一个路径(如其他答案中所述),但之前的答案似乎忽略了addRoundedRect函数调用,该函数为每个角提供半径。

变量

private val path = Path()
private val paint = Paint()

设置画图软件

paint.color = Color.RED
paint.style = Paint.Style.FILL

更新尺寸变化的路径

将此代码放在不是onDraw方法中的其他地方,例如视图的onMeasure方法或可绘制对象的onBoundChange方法。如果它不会发生变化(就像这个例子一样),您可以将此代码放在设置画笔的地方。

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)

这段代码创建了一个100x100的圆角矩形,其顶部角落被25半径圆角化。 绘制路径 在视图的onDraw方法或可绘制对象的draw方法中调用此方法。
canvas.drawPath(path, paint)

7

如果半径是高度的一半,则使用Path#arcTo()版本来绘制圆角边。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接