使用 Android 中的路径自定义形状绘图

Custom Shape drawing using Path in Android

我正在尝试在 canvas 上绘制自定义形状,目前看起来像这样:

和 这是我想要实现的目标:

除此之外,我希望 RGB 边框的属性是可配置的,例如。应该能够根据需要更改笔划宽度。但是,我面临几个问题:

  1. 无法去除倒三角的底边(底部的RGB边框线不应该是直的)
  2. 如果我尝试更改 RGB 线的宽度(使用 paint.setStrokeWidth()),它会引入不需要的间隙 b/w 它们,而我希望它们是连续的。我确定我在计算错误,但无法弄清楚。
  3. 我有过这样的经历:使用 lineTo on Path 沿着视图边缘绘制一条线是用 Paint 上设置的笔划宽度的一半绘制的。但是,我无法找到任何阅读 material 的内容。谁能赐教一下吗?

自定义view的onDraw方法如下:

override fun onDraw(canvas: Canvas?) {
    outerBorderPath.reset()
    mainBorderPath.reset()
    innerBorderPath.reset()


    ///let's draw our content first
    canvas?.let { drawingCanvas ->
        outerBorderPath.addRect(outerBorderWidth.toFloat(),outerBorderWidth.toFloat(),width.toFloat() - outerBorderWidth, (height - arrowHeight - outerBorderWidth).toFloat(), Path.Direction.CW)
        outerBorderPath.addPath(mArrowPath, width.toFloat() - arrowWidth - 100,
            (height - arrowHeight - outerBorderWidth).toFloat()
        )
        drawingCanvas.drawPath(outerBorderPath, outerBorderPaint)
        mainBorderPath.addRect(outerBorderWidth + mainBorderWidth.toFloat(),
        outerBorderWidth + mainBorderWidth.toFloat(),
            width.toFloat() - outerBorderWidth - mainBorderWidth,
            (height - arrowHeight - outerBorderWidth - mainBorderWidth).toFloat(),
            Path.Direction.CW
            )

        mainBorderPath.addPath(mainArrowPath, width.toFloat() - arrowWidth + (outerBorderWidth/2) - 100,
            (height - arrowHeight - outerBorderWidth - mainBorderWidth).toFloat()
            )
        drawingCanvas.drawPath(mainBorderPath, mainBorderPaint)

        innerBorderPath.addRect(outerBorderWidth + mainBorderWidth + innerBorderWidth.toFloat(),
            outerBorderWidth + mainBorderWidth*1f + innerBorderWidth,
            width.toFloat() - outerBorderWidth - mainBorderWidth*1f - innerBorderWidth,
            (height - arrowHeight - outerBorderWidth - mainBorderWidth*1f - innerBorderWidth).toFloat(),
            Path.Direction.CW
            )
        innerBorderPath.addPath(innerArrowPath, width.toFloat() - arrowWidth + (outerBorderWidth + mainBorderWidth)/2 - 100,
            (height - arrowHeight - outerBorderWidth - mainBorderWidth - innerBorderWidth).toFloat()
            )
        drawingCanvas.drawPath(innerBorderPath, innerBorderPaint)
    }

    ///translate canvas to the child can be drawn now
    canvas?.save()
    super.onDraw(canvas)
    canvas?.restore()
}

另外,视图class的onMeasure如下:

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    setMeasuredDimension(
        (measuredWidth + (2 * outerBorderWidth) + (2 * innerBorderWidth) + (2 * innerPadding)).toInt(),
        (measuredHeight + (2 * outerBorderWidth) + (2 * innerBorderWidth) + (2 * innerPadding) + arrowHeight).toInt()
    )
}

I have experienced that drawing a line along the view edge using lineTo on Path is drawn with half of the stroke width set on the Paint. However, I am unable to find out any reading material on the same. Can someone please enlighten me?

我从未找到任何关于此的官方文档,但我可以确认这不仅发生在 Path 上,而且发生在 Canvas.drawRect()Canvas.drawArc() 上,可能是因为 Path在引擎盖下使用(例如参见 [​​=29=])。这种行为是有道理的:为什么要允许 View 在其边界之外绘制?但我也希望我们不必通过反复试验来学习...

If I try to change the width of the RGB lines (using paint.setStrokeWidth()), it introduces undesired gaps b/w them, whereas I want them to be continuous. I am sure I am making some calculation mistake, but can't figure it out.

以下屏幕截图并排显示了两个不同规格的模拟器。可以看到 View 并不总是以相同的方式呈现。差异可能是由于四舍五入 - 最后你必须将 Float 映射到 Int 值,因为设备上的像素数是 Int 值。

由于 Path 是一个接一个地画的,一种方法是使外边框和主边框更宽,这样就没有间隙了。

Not able to remove the base of the inverted triangle (The RGB border lines should not be straight at the bottom)

倒三角形的底边是 Rect 的一部分,用于配置 Path。您可以通过使用多个 lineTo() 而不是 addRect()

来解决这个问题
fun Path.createShapeWithPadding(padding: Int, arrowStartX: Int, arrowStartY:Int, width: Int, height: Int, arrowWidth: Int, arrowHeight: Int ) {

    val paddingF = padding.toFloat()
    moveTo(paddingF, paddingF)
    lineTo(width - paddingF, paddingF)
    lineTo(width - paddingF, arrowStartY.toFloat())
    lineTo(arrowStartX.toFloat(), arrowStartY.toFloat())
    rLineTo(-arrowWidth / 2f, arrowHeight.toFloat())
    rLineTo(-arrowWidth / 2f, -arrowHeight.toFloat())
    lineTo(paddingF, arrowStartY.toFloat())
    close()
}

使用onDraw()中的扩展函数:

override fun onDraw(canvas: Canvas?) {
    outerBorderPath.reset()
    mainBorderPath.reset()
    innerBorderPath.reset()
    
    canvas?.let { drawingCanvas ->

        val arrowX = width - 100
        val arrowY = height - arrowHeight - outerBorderWidth
        outerBorderPath.apply {
            createShapeWithPadding(outerBorderWidth, arrowX, arrowY, width, height, arrowWidth, arrowHeight)
        }

        drawingCanvas.drawPath(outerBorderPath, outerBorderPaint)

        mainBorderPath.apply {
            createShapeWithPadding(
                mainBorderWidth + outerBorderWidth,
                arrowX - (outerBorderWidth / 2) ,
                arrowY - mainBorderWidth,
                width,
                height,
                arrowWidth - outerBorderWidth,
                arrowHeight - outerBorderWidth
            )
        }
        drawingCanvas.drawPath(mainBorderPath, mainBorderPaint)

        innerBorderPath.apply {
            createShapeWithPadding(
                outerBorderWidth + mainBorderWidth + innerBorderWidth,
                arrowX - (outerBorderWidth + mainBorderWidth) / 2,
                arrowY - (mainBorderWidth + innerBorderWidth),
                width,
                height,
                arrowWidth - (outerBorderWidth + mainBorderWidth),
                arrowHeight - (outerBorderWidth + mainBorderWidth)
            )
        }
        drawingCanvas.drawPath(innerBorderPath, innerBorderPaint)

    }

    canvas?.save()
    super.onDraw(canvas)
    canvas?.restore()
}