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

Jetpack Compose:如何测试Canvas中DrawScope绘制的图形?

解决Jetpack Compose中Hangman Canvas元素的独立语义识别问题

方案1:将身体部位拆分为独立的带语义修饰的Composable

把刽子手的每个身体部位(头、躯干、手臂、腿等)拆成单独的Composable,给每个组件添加Modifier.semantics标记专属的内容描述,这样每个部位会成为独立的语义节点,方便测试或辅助功能识别。

示例代码:

@Composable
fun HangmanBody(
    showHead: Boolean,
    showBody: Boolean,
    showLeftArm: Boolean,
    showRightArm: Boolean,
    showLeftLeg: Boolean,
    showRightLeg: Boolean
) {
    Box(Modifier.size(200.dp)) {
        // 绘制支架(固定元素,也可添加语义描述)
        Canvas(Modifier.matchParentSize()) {
            drawLine(
                color = Color.Black,
                start = Offset(size.width / 2, 0f),
                end = Offset(size.width / 2, size.height * 0.2f),
                strokeWidth = 4.dp.toPx()
            )
            // 补充绘制支架的其他部分
        }

        // 头部组件
        if (showHead) {
            Canvas(
                Modifier
                    .size(60.dp)
                    .align(Alignment.TopCenter)
                    .offset(y = 20.dp)
                    .semantics { contentDescription = "Hangman head" }
            ) {
                drawCircle(color = Color.Black, radius = size.width / 2, strokeWidth = 4.dp.toPx())
            }
        }

        // 躯干组件
        if (showBody) {
            Canvas(
                Modifier
                    .size(20.dp, 80.dp)
                    .align(Alignment.TopCenter)
                    .offset(y = 80.dp)
                    .semantics { contentDescription = "Hangman body" }
            ) {
                drawLine(
                    color = Color.Black,
                    start = Offset(size.width / 2, 0f),
                    end = Offset(size.width / 2, size.height),
                    strokeWidth = 4.dp.toPx()
                )
            }
        }

        // 左臂组件
        if (showLeftArm) {
            Canvas(
                Modifier
                    .size(60.dp, 60.dp)
                    .align(Alignment.TopCenter)
                    .offset(y = 80.dp, x = -30.dp)
                    .semantics { contentDescription = "Hangman left arm" }
            ) {
                drawLine(
                    color = Color.Black,
                    start = Offset(size.width, 0f),
                    end = Offset(0f, size.height),
                    strokeWidth = 4.dp.toPx()
                )
            }
        }

        // 其余身体部位按相同逻辑实现
    }
}

方案2:在单个Canvas中手动注入语义节点

如果不想拆分Composable,可在Canvas的DrawScope内,为每个绘制元素手动添加语义节点,需要指定元素在Canvas内的边界矩形,让语义系统识别其位置范围。

示例代码:

@Composable
fun HangmanCanvas(errorCount: Int) {
    Canvas(Modifier.size(200.dp).semantics { contentDescription = "Hangman" }) {
        val headRadius = 30.dp.toPx()
        val headCenter = Offset(size.width / 2, 20.dp.toPx() + headRadius)
        val headBounds = Rect(
            headCenter.x - headRadius,
            headCenter.y - headRadius,
            headCenter.x + headRadius,
            headCenter.y + headRadius
        )

        // 绘制支架
        drawLine(
            color = Color.Black,
            start = Offset(size.width / 2, 0f),
            end = Offset(size.width / 2, size.height * 0.2f),
            strokeWidth = 4.dp.toPx()
        )

        // 绘制头部并注入语义节点
        if (errorCount >= 1) {
            drawCircle(Color.Black, headRadius, headCenter, 4.dp.toPx())
            semantics(
                properties = { contentDescription = "Hangman head" },
                boundsInParent = headBounds
            ) {}
        }

        // 绘制躯干并注入语义节点
        val bodyTop = headCenter.y + headRadius
        val bodyBottom = bodyTop + 80.dp.toPx()
        val bodyBounds = Rect(
            size.width / 2 - 10.dp.toPx(),
            bodyTop,
            size.width / 2 + 10.dp.toPx(),
            bodyBottom
        )
        if (errorCount >= 2) {
            drawLine(
                Color.Black,
                Offset(size.width / 2, bodyTop),
                Offset(size.width / 2, bodyBottom),
                4.dp.toPx()
            )
            semantics(
                properties = { contentDescription = "Hangman body" },
                boundsInParent = bodyBounds
            ) {}
        }
    }
}

测试时的元素查找

采用上述任意方案后,测试阶段可通过onNode(hasContentDescription("Hangman head"))这类匹配器,定位对应语义节点,验证其是否按错误次数正确显示。

内容的提问来源于stack exchange,提问作者Varsha Kulkarni

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 14:18:11