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
相关产品推荐
相关产品推荐

