【问题标题】:How to use android canvas to draw a Rectangle with only topleft and topright corners round?如何使用android画布绘制一个只有左上角和右上角的矩形?
【发布时间】:2021-08-27 16:50:26
【问题描述】:

我找到了一个所有 4 个角都是圆的矩形的函数,但我只想让前 2 个角变圆。我能做什么?

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

【问题讨论】:

  • 你不能只画一个圆角矩形,然后在下半部分画一个普通的矩形(用普通的圆角覆盖圆角?)

标签: android android-canvas draw


【解决方案1】:

使用路径。它具有适用于少于 21 个 API 的优势(Arc 也因此受到限制,这就是我四边形的原因)。这是一个问题,因为不是每个人都有棒棒糖。但是,您可以指定一个 RectF 并使用它设置值并使用 arc 回到 API 1,但是您将无法使用静态(无需声明新对象来构建对象)。

绘制圆角矩形:

    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 的作用。直接到那里的控制点。

如果你想做这一切但不关心棒棒糖之前的东西,并紧急坚持如果你的 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 实际上绘制了一个圆角矩形,底部两位没有圆角。

【讨论】:

  • 在我看来,我认为相对路径函数要好得多,因为您只需要担心要绘制路径的一点。
  • @chx101 是的,好的,将其重新设置为相对并从曲线开始,以便 close() 可以覆盖 lineto。
  • @Tatrize 哇,你重写了整件事吧?
  • 那里有部分原始答案。我基本上最终大部分都废弃了原始代码,实际上完全提供了实际请求的答案。但是,更多的是因为弧线很粗鲁,所以我不得不反转方向,然后重新排列它们,将最后两个放在底部,以便轻松移除它们。然后我检查了 W3 Rect 以了解如何处理各种 RX、RY 值的典型标准实现(如果为负,则选择 0 而不是抛出错误)。 w3.org/TR/SVG/shapes.html#RectElement
  • 我突然想到,即使没有圆弧,也可以用二次贝塞尔曲线而不是四边形来制作更圆的曲线。也就是说,您将在 1,c 和 c,1(相对于象限)处进行控制点,其中 C = (4/3)*tan(pi/8) 或对 C 进行稍微改进的 spencer mortensen 值。四边形用拐角处的控制点可以,但实际上有点幼稚。
【解决方案2】:

我会画两个矩形:

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

或者类似的东西,你只需将它们重叠起来,这样上角就会是圆形的。最好你应该为此编写一个方法

【讨论】:

  • 我需要为整个矩形设置 alpha,重叠部分与其他部分的 alpha 值不同
  • @V.Kalyuzhnyu 答案中使用的方法签名自 API 1 起可用,您一定是在谈论 API 21 中可用的 drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, Paint paint)
  • @virsir paint.setColor(Color.argb(200, 0,0,0)); 会将 alpha 设置为绘画。
【解决方案3】:

对于 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)

【讨论】:

  • 这是我一直在寻找的圣杯!谢谢老兄!
  • 正确解决方案
  • 好答案,用路径填充类型拉出我的头发,这解决了我的问题
【解决方案4】:

我更改了this 答案,以便您可以设置要圆的角和要锐角的角。也适用于前棒棒糖

使用示例

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

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

【讨论】:

  • 你应该用适当的贝塞尔弧近似角来修正我的答案。 spencermortensen.com/articles/bezier-circle 并转换使用 C 风格的按位方式作为角落而不是一堆布尔值。静态最终 int SHARP_TOP_RIGHT = 0b0001,静态最终 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) 工作正常。所有静态最终。
  • 完美!这就是我要找的。!谢谢哥们!
  • 感激不尽!太棒了!你刚刚为我节省了很多时间!谢谢!
  • 非常感谢,我在模糊视图之前使用它:@Override protected void onDraw(Canvas canvas) { Path clipPath = RoundedRect(0, 0, getWidth(), getHeight(), mCornerRadius,mCornerRadius,mTopLeftCorner,mTopRightCorner,mBottomRightCorner,mBottomLeftCorner); canvas.clipPath(clipPath);超级.onDraw(画布); drawBlurredBitmap(canvas, mBlurredBitmap, mOverlayColor); }
【解决方案5】:

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

【讨论】:

  • 它会重叠,颜色 alpha 会不同
  • drawRoundRect() 需要 API 级别 21。
【解决方案6】:

您可以通过使用 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)

【讨论】:

    【解决方案7】:
    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 放在代码。
    【解决方案8】:

    我通过以下步骤实现了这一点。

    这些是圆角矩形看起来整洁的先决条件

    • 边缘的半径必须等于(矩形的高度/2)。这是因为如果它有任何不同的值,那么曲线与矩形直线相交的地方就不会是

    接下来是绘制圆角矩形的步骤。

    • 首先我们在左右两边画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);
    }
    

    现在这会产生一个非常漂亮的圆角矩形,如下所示

    【讨论】:

      【解决方案9】:

      这是一个老问题,但我想添加我的解决方案,因为它使用本机 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)
      

      【讨论】:

        【解决方案10】:

        绘制实心边的一种简单而有效的方法是使用剪切 - 矩形剪切基本上是免费的,而且与自定义路径相比,要编写的代码要少得多。

        如果我想要一个 300x300 的矩形,左上角和右上角四舍五入 50 像素,你可以这样做:

        canvas.save();
        canvas.clipRect(0, 0, 300, 300);
        canvas.drawRoundRect(new RectF(0, 0, 300, 350), 50, 50, paint);
        canvas.restore();
        

        此方法仅适用于 2 或 3 个相邻角的舍入,因此它的可配置性比基于路径的方法稍差,但使用圆形矩形更有效,因为 drawRoundRect() 是完全硬件加速的(即,镶嵌成三角形),而 drawPath() 总是回退到软件渲染(软件绘制路径位图,并将其上传到 GPU 上缓存)。

        对于不经常绘制的小幅画来说,这不是一个大的性能问题,但如果您正在制作路径动画,则软件绘制的成本可能会使您的帧时间更长,并增加丢帧的机会。路径掩码也会消耗内存。

        如果您确实想使用基于路径的方法,我建议您使用 GradientDrawable 来简化代码行(假设您不需要设置自定义着色器,例如绘制位图)。

        mGradient.setBounds(0, 0, 300, 300);
        mGradient.setCornerRadii(new int[] {50,50, 50,50, 0,0, 0,0});
        

        使用GradientDrawable#setCornerRadii(),您可以将任意角设置为任意圆角,并在状态之间合理地设置动画。

        【讨论】:

        • 剪辑没有抗锯齿;裁剪圆形东西时出现问题
        【解决方案11】:

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

        【讨论】:

          【解决方案12】:

          您可以使用Canvas 中的drawLine()drawArc() 函数逐块绘制。

          【讨论】:

          • 但是在 API 21 中引入了 drawArc() 方法,还有其他方法我可以做同样的事情吗?就像画一条弧线一样?
          • 从 API 1 开始就有另一个版本的 drawArc()。
          【解决方案13】:

          这是我对上述问题的回答。在这里,我创建了 Kotlin 扩展函数,它使用 Path 以及 quadTo 函数,它也可以在较低级别的 API 中使用。

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

          我们可以使用画布对象调用该函数,并将RectF 传递给我们要应用曲线的维度。

          此外,我们可以传递布尔值来表示要圆角的角。 可以进一步自定义此答案以接受各个角的半径。

          【讨论】:

          • 如果我在自己做同样的事情之前找到了这部分代码,那就太好了。大声笑!
          【解决方案14】:

          使用PaintDrawable 会更好:

              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)
          

          【讨论】:

            【解决方案15】:

            用左圆角绘制圆形矩形

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

            【讨论】:

            • 此方法不为矩形提供圆角,它为某些角提供圆角但不是全部,结果形状失真。我猜你必须努力解决这个问题。
            • 您不应该在 onDraw 方法中创建新对象 - 使用预分配路径
            【解决方案16】:

            也许下面的代码可以帮到你

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2019-11-18
              • 1970-01-01
              • 1970-01-01
              • 2019-07-25
              • 2021-08-21
              相关资源
              最近更新 更多