You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

两端带缺口的Gauge组件:BlendMode.Clear无法生成透明缺口求助

问题描述

我尝试创建一款两端带有缺口的Gauge组件,但BlendMode.Clear未正常工作——原本应呈现透明状态的缺口显示为灰黑色三角形,不知道原因出在哪。

当前实现代码

@Composable
fun Gauge() {
    Box {
        val progress by remember { mutableFloatStateOf(0.6f) }
        Canvas(
            modifier = Modifier
                .fillMaxSize()
                .padding(8.dp)
        ) {
            val progressConvertedValue = progress * 300

            drawArcWithNotches(
                brush = Brush.sweepGradient(
                    0.25f to Color(0xFFDAF3F7),
                    0.30f to Color(0xFF094863),
                    0.60f to Color(0xFFD2F4FF),
                    0.75f to Color(0xFFD2F4FF),
                    0.85f to Color(0xFF94DDFD),
                    1.00f to Color(0xFFDAF3F7),
                ),
                startAngle = 120f,
                sweepAngle = progressConvertedValue,
                useCenter = false,
                style = Stroke(20f)
            )

            drawGaugeMarks()
        }

        Text(
            text = "${progress * 100}%",
            modifier = Modifier.align(Alignment.Center),
        )
    }
}

private fun DrawScope.drawArcWithNotches(
    brush: Brush,
    startAngle: Float,
    sweepAngle: Float,
    useCenter: Boolean,
    style: Stroke
) {
    val radius = size.width / 2
    val center = Offset(size.width / 2, size.height / 2)
    val notchSize = 20f

    // Draw the main arc
    drawArc(
        brush = brush,
        startAngle = startAngle,
        sweepAngle = sweepAngle,
        useCenter = useCenter,
        style = style
    )

    // Calculate the positions of the start and end of the arc
    val startAngleRadians = Math.toRadians(startAngle.toDouble())
    val endAngleRadians = Math.toRadians((startAngle + sweepAngle).toDouble())

    val startX = (center.x + radius * cos(startAngleRadians)).toFloat()
    val startY = (center.y + radius * sin(startAngleRadians)).toFloat()
    val endX = (center.x + radius * cos(endAngleRadians)).toFloat()
    val endY = (center.y + radius * sin(endAngleRadians)).toFloat()

    // Draw the notches as transparent areas using BlendMode.Clear
    val startNotchPath = Path().apply {
        moveTo(startX, startY)
        lineTo(
            startX - notchSize * cos(startAngleRadians + Math.PI / 4).toFloat(),
            startY - notchSize * sin(startAngleRadians + Math.PI / 4).toFloat()
        )
        lineTo(
            startX - notchSize * cos(startAngleRadians - Math.PI / 4).toFloat(),
            startY - notchSize * sin(startAngleRadians - Math.PI / 4).toFloat()
        )
        close()
    }

    val endNotchPath = Path().apply {
        moveTo(endX, endY)
        lineTo(
            endX - notchSize * cos(endAngleRadians - Math.PI / 4).toFloat(),
            endY - notchSize * sin(endAngleRadians - Math.PI / 4).toFloat()
        )
        lineTo(
            endX - notchSize * cos(endAngleRadians + Math.PI / 4).toFloat(),
            endY - notchSize * sin(endAngleRadians + Math.PI / 4).toFloat()
        )
        close()
    }

    // Clear the areas for the notches
    drawPath(
        path = startNotchPath,
        color = Color.Transparent,
        blendMode = BlendMode.Clear
    )

    drawPath(
        path = endNotchPath,
        color = Color.Transparent,
        blendMode = BlendMode.Clear
    )
}

当前效果

当前效果截图

期望效果

期望效果截图


解决方案

原因分析

BlendMode.Clear在Compose Canvas中直接使用时,会清除整个Canvas的绘制缓冲区(包括默认背景),而非仅清除目标弧的区域,所以缺口处显示的是Canvas默认的灰黑色背景,而非透明。

修复代码

将弧和缺口的绘制包裹在独立图层中,限制Clear操作的影响范围,修改后的drawArcWithNotches函数如下:

private fun DrawScope.drawArcWithNotches(
    brush: Brush,
    startAngle: Float,
    sweepAngle: Float,
    useCenter: Boolean,
    style: Stroke
) {
    val radius = size.width / 2
    val center = Offset(size.width / 2, size.height / 2)
    val notchSize = 20f

    // 创建独立图层,限制Clear操作仅影响该图层内的内容
    withLayer {
        // 绘制主弧
        drawArc(
            brush = brush,
            startAngle = startAngle,
            sweepAngle = sweepAngle,
            useCenter = useCenter,
            style = style
        )

        // 计算缺口位置(逻辑不变)
        val startAngleRadians = Math.toRadians(startAngle.toDouble())
        val endAngleRadians = Math.toRadians((startAngle + sweepAngle).toDouble())

        val startX = (center.x + radius * cos(startAngleRadians)).toFloat()
        val startY = (center.y + radius * sin(startAngleRadians)).toFloat()
        val endX = (center.x + radius * cos(endAngleRadians)).toFloat()
        val endY = (center.y + radius * sin(endAngleRadians)).toFloat()

        // 创建缺口路径(逻辑不变)
        val startNotchPath = Path().apply {
            moveTo(startX, startY)
            lineTo(
                startX - notchSize * cos(startAngleRadians + Math.PI / 4).toFloat(),
                startY - notchSize * sin(startAngleRadians + Math.PI / 4).toFloat()
            )
            lineTo(
                startX - notchSize * cos(startAngleRadians - Math.PI / 4).toFloat(),
                startY - notchSize * sin(startAngleRadians - Math.PI / 4).toFloat()
            )
            close()
        }

        val endNotchPath = Path().apply {
            moveTo(endX, endY)
            lineTo(
                endX - notchSize * cos(endAngleRadians - Math.PI / 4).toFloat(),
                endY - notchSize * sin(endAngleRadians - Math.PI / 4).toFloat()
            )
            lineTo(
                endX - notchSize * cos(endAngleRadians + Math.PI / 4).toFloat(),
                endY - notchSize * sin(endAngleRadians + Math.PI / 4).toFloat()
            )
            close()
        }

        // 在图层内执行Clear,仅清除弧的区域,透出下方透明背景
        drawPath(
            path = startNotchPath,
            color = Color.Transparent,
            blendMode = BlendMode.Clear
        )

        drawPath(
            path = endNotchPath,
            color = Color.Transparent,
            blendMode = BlendMode.Clear
        )
    }
}

关键说明

  • withLayer会创建离屏缓冲区,所有绘制操作都在该缓冲区中进行,BlendMode.Clear只会清除缓冲区内部的内容,不会影响Canvas的全局背景,缺口处因此会呈现透明状态,透出父布局的背景。
  • 确保父布局(如示例中的Box)设置了你需要的背景色,这样缺口会正确透出该颜色,达到期望效果。

内容的提问来源于stack exchange,提问作者Jéwôm'

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 16:44:52