【问题标题】:How to draw a path with rounded corners如何绘制圆角路径
【发布时间】:2023-07-19 16:01:01
【问题描述】:

我应该像这张照片一样画一个 CustomView。

但它们并不相同。拐角笔画不同。

我使用 2 个分隔的 Path 来绘制顶部形状: 黄色背景的第一个:

   private val paint = Paint().apply {
        isAntiAlias = false                    // pass true does not make change
        color = Color.YELLOW
        style = Paint.Style.FILL_AND_STROKE    // pass only FILL does not make change
        }

第二个是:

private val strokePaint = Paint().apply {
        isAntiAlias = false                   // pass true does not make change
        color = Color.BLACK
        strokeWidth = 2.toPx().toFloat()
        style = Paint.Style.STROKE
    }

onDraw() 函数中我由他们绘制:

override fun onDraw(canvas: Canvas) {
        drawPath()

        canvas.drawPath(path, paint)
        canvas.drawPath(path, strokePaint)

        // at the end, draw text and default things to avoid overlapping with background
        super.onDraw(canvas)

    }

更新: 现在我画了它的指针有两个边。

并使用此路径进行绘制:

 private fun drawPath() {
    path.run {
        moveTo(left + radius, top)

        if (_side == SIDE_TOP) {
            lineTo(pointerX - pointerSize / 2, top)
            lineTo(pointerX, rect.top)
            lineTo(pointerX + pointerSize / 2, top)
        }
        lineTo(right - radius, top)

        arcTo(topRightRect, 270F, 90F, false)
        lineTo(right, bottom - radius)
        arcTo(bottomRightRect, 0F, 90F, false)

        if (_side == SIDE_BOTTOM) {
            lineTo(pointerX + pointerSize / 2, bottom)
            lineTo(pointerX, rect.bottom)
            lineTo(pointerX - pointerSize / 2, bottom)
        }
        lineTo(left + radius, bottom)

        arcTo(bottomLeftRect, 90F, 90F, false)
        lineTo(left, top + radius)
        arcTo(topLeftRect, 180F, 90F, false)
        close()
    }
}

【问题讨论】:

    标签: android android-canvas android-custom-view custom-view


    【解决方案1】:

    Canvas 有一些预定义的方法来绘制圆形和矩形等常见形状。在您的场景中,您可以使用drawRoundRect,它需要RectF 来绘制一个矩形。

    这是一个例子:

    class RoundedRect @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
    ) : View(context, attrs, defStyleAttr) {
    
        private val roundCorner = 32f
    
        private val paint = Paint().apply {
            color = Color.YELLOW
            style = Paint.Style.FILL
            isAntiAlias = true
        }
    
        private val strokePaint = Paint().apply {
            color = Color.BLACK
            strokeWidth = 4f
            style = Paint.Style.STROKE
            isAntiAlias = true
        }
    
        private var rect = RectF(0f, 0f, 0f, 0f)
    
        override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
            super.onSizeChanged(w, h, oldw, oldh)
    
            rect = RectF(0f, 0f, w.toFloat(), h.toFloat())
        }
    
        override fun onDraw(canvas: Canvas) {
            super.onDraw(canvas)
    
            canvas.drawRoundRect(rect, roundCorner, roundCorner, paint)
            canvas.drawRoundRect(rect, roundCorner, roundCorner, strokePaint)
        }
    }
    

    顺便说一句,如果要使用路径绘制圆角,则必须将pathEffect 设置为CornerPathEffect

    这是一个例子:

    
    class RoundedRectUsingPath @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
    ) : View(context, attrs, defStyleAttr) {
    
        private val roundCorner = 32f
    
        private val paint = Paint().apply {
            color = Color.YELLOW
            isAntiAlias = true
            pathEffect = CornerPathEffect(roundCorner)
            strokeCap = Paint.Cap.ROUND
        }
    
        private val strokePaint = Paint().apply {
            color = Color.BLACK
            strokeWidth = 4f
            isAntiAlias = true
            style = Paint.Style.STROKE
            pathEffect = CornerPathEffect(roundCorner)
        }
    
        private var path = Path()
        private val offset = 50f
    
        override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
            super.onSizeChanged(w, h, oldw, oldh)
            path = Path().apply {
                moveTo(offset, offset)
                lineTo(w.toFloat() - offset, offset)
                lineTo(w.toFloat() - offset, h.toFloat() - offset)
                lineTo(offset, h.toFloat() - offset)
            }
            path.close()
    
        }
    
        override fun onDraw(canvas: Canvas) {
            super.onDraw(canvas)
    
            canvas.drawPath(path, paint)
            canvas.drawPath(path, strokePaint)
        }
    }
    

    【讨论】:

    • 感谢您的回答。但我想通过路径绘制。因为我不想画出精确的 RoundRectangle。 (我的形状就像消息泡泡)
    【解决方案2】:

    我想我为你找到了解决方案

    我花了很长时间才弄清楚那里发生了什么。

    请注意,我的解决方案仅绘制圆角矩形,而不使用 路径,而是drawRoundRectCanvas的预定义方法。

    我正在创建一个带有边框/笔划的自定义进度条。我首先想到的是创建一个Paint 实例,将绘画的样式设置为Paint.Style.STROKE,然后绘制一个圆角矩形。

    override fun dispatchDraw(canvas: Canvas?) {
        // I do the actual initialisations outside of dispatchDraw(). This is just an example.
        val paint: Paint = Paint()
        val strokeWidth = 4f
        val cornerRadius = 10f
        val strokeColor = Color.RED
        val strokeRect = RectF().apply {
            set(0f, 0f, width.toFloat(), height.toFloat())
        }
        
        paint.style = Paint.Style.STROKE
        paint.strokeWidth = strokeWidth
        paint.color = strokeColor
        canvas?.drawRoundRect(strokeRect, cornerRadius, cornerRadius, paint)
    }
    

    注意:上面的代码只画了笔画,如果你想在里面也画进度,你可以新建一个矩形并设置样式 油漆Paint.Style.FILL。它应该很简单 去做吧。

    嗯,这是我使用上面的代码绘制进度笔划时得到的,看起来不太好。

    首先我认为角落没有正确绘制,我应该增加角落半径,但这不是一个解决方案。在做了一些研究和测试不同的实现之后,我发现问题与角落无关,而与视图本身有关。我的中风刚刚被剪掉,没有完全可见。

    添加insets 是解决我的问题的关键,而且操作起来非常简单。

    这是修改后的代码:

    override fun dispatchDraw(canvas: Canvas?) {
        // I do the actual initialisations outside of dispatchDraw(). This is just an example.
        val paint: Paint = Paint()
        val strokeWidth = 2f
        val cornerRadius = 10f
        val strokeColor = Color.RED
        val strokeRect = RectF().apply {
            set(0f, 0f, width.toFloat(), height.toFloat())
            inset(paint.strokeWidth / 2, paint.strokeWidth / 2)
        }
        
        paint.style = Paint.Style.STROKE
        paint.strokeWidth = strokeWidth
        paint.color = strokeColor
        canvas?.drawRoundRect(strokeRect, cornerRadius, cornerRadius, paint)
    }
    

    我只添加了一行,将insets 设置为笔划宽度的一半,即切割的确切尺寸。

    inset(paint.strokeWidth / 2, paint.strokeWidth / 2)
    

    我还稍微修改了strokeWidth 使其看起来不错(2f 而不是4f)。你可以 稍后更改以符合您的设计要求。

    在那之后,你就有了预期的中风。

    希望我的解决方案能帮助您节省时间和精力!

    【讨论】: