如何为Jetpack Compose中带GenericShape的按钮设置规则边框?
如何在Jetpack Compose中为自定义形状按钮实现规则边框?
问题场景
我在Jetpack Compose中给按钮设置了自定义GenericShape和1.dp的边框,但实际显示的边框不规则,呈现类似阴影的效果(已将elevation设为null)。
最初的实现代码
val shape = GenericShape { size, _ -> moveTo(235f, 15f) cubicTo(250f, 30f, 260f, 60f, 240f, 65f) cubicTo(195f, 75f, 195f, 50f, 200f, 35f) cubicTo(205f, 20f, 220f, 0f, 235f, 15f) close() } Button( modifier = Modifier.fillMaxSize(), colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colors.surface), border = BorderStroke(1.dp, MaterialTheme.colors.onSurface), onClick = {}, shape = shape, elevation = null, content = {}, )
2023年3月21日更新:尝试SVG路径缩放方案
根据建议修改了路径创建与缩放逻辑,但视觉效果仍未改善:
val shape = GenericShape { size, _ -> val path = PathParser.createPathFromPathData("M 235 15 C 250 30 260 60 240 65 C 195 75 195 50 200 35 C 205 20 220 0 235 15 Z") val matrix = Matrix() // SVG原始坐标空间为[0, 0, initialSVG_Width, initialSVG_Height] val scale = minOf(size.width / initialSVG_Width, size.height / initialSVG_Height) matrix.setScale(scale, scale) path.transform(matrix) addPath(path.asComposePath()) } Button( modifier = Modifier.fillMaxSize(), colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colors.surface), border = BorderStroke(1.dp, MaterialTheme.colors.onSurface), onClick = {}, elevation = null, shape = shape ) {}
尝试Modifier.draw方案
使用drawWithCache绘制路径,视觉效果符合预期,但点击非绿色区域时也会触发点击事件:
Spacer(modifier = Modifier .fillMaxSize() .drawWithCache { val path = PathParser.createPathFromPathData("M 37.175 165 C 70 135 120 155 140.87 175 C 136.5 190 135.5 210 135 217.435 C 85 270 90 280 35 217.428 Q 64 194.5 37.175 165 Z") val matrix = Matrix() val scale = minOf(size.width / initialSVG_Width, size.height / initialSVG_Height) matrix.setScale(scale, scale) path.transform(matrix) val composePath = path.asComposePath() onDrawWithContent { drawPath(path = composePath, color = Color.Green, style = Fill) drawPath(path = composePath, color = Color.Black, style = Stroke(width = 2f)) } } .clickable { Toast.makeText(context, "Click!", Toast.LENGTH_SHORT).show() }, )
解决方案
1. 修正GenericShape的坐标适配逻辑
硬编码的路径坐标未适配按钮实际尺寸,导致边框变形。需基于按钮size计算相对坐标,保证路径缩放后居中且无变形:
val shape = GenericShape { size, _ -> // 定义SVG原始路径的宽高 val originalWidth = 260f val originalHeight = 75f // 计算等比例缩放系数 val scale = minOf(size.width / originalWidth, size.height / originalHeight) // 计算居中偏移量 val offsetX = (size.width - originalWidth * scale) / 2f val offsetY = (size.height - originalHeight * scale) / 2f // 基于缩放和偏移绘制路径 moveTo(offsetX + 235f * scale, offsetY + 15f * scale) cubicTo( offsetX + 250f * scale, offsetY + 30f * scale, offsetX + 260f * scale, offsetY + 60f * scale, offsetX + 240f * scale, offsetY + 65f * scale ) cubicTo( offsetX + 195f * scale, offsetY + 75f * scale, offsetX + 195f * scale, offsetY + 50f * scale, offsetX + 200f * scale, offsetY + 35f * scale ) cubicTo( offsetX + 205f * scale, offsetY + 20f * scale, offsetX + 220f * scale, offsetY + 0f * scale, offsetX + 235f * scale, offsetY + 15f * scale ) close() } Button( modifier = Modifier.fillMaxSize(), colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colors.surface), border = BorderStroke(1.dp, MaterialTheme.colors.onSurface), onClick = {}, shape = shape, elevation = null, content = {} )
2. 修复draw方案的点击区域问题
通过pointerInput自定义点击判断逻辑,仅响应路径内部的点击:
Spacer(modifier = Modifier .fillMaxSize() .drawWithCache { val path = PathParser.createPathFromPathData("M 37.175 165 C 70 135 120 155 140.87 175 C 136.5 190 135.5 210 135 217.435 C 85 270 90 280 35 217.428 Q 64 194.5 37.175 165 Z") val matrix = Matrix() val scale = minOf(size.width / initialSVG_Width, size.height / initialSVG_Height) matrix.setScale(scale, scale) path.transform(matrix) val composePath = path.asComposePath() onDrawWithContent { drawPath(path = composePath, color = Color.Green, style = Fill) drawPath(path = composePath, color = Color.Black, style = Stroke(width = 2f)) } } .pointerInput(Unit) { detectTapGestures { offset -> // 重新创建适配尺寸的路径,判断点击点是否在路径内 val path = PathParser.createPathFromPathData("M 37.175 165 C 70 135 120 155 140.87 175 C 136.5 190 135.5 210 135 217.435 C 85 270 90 280 35 217.428 Q 64 194.5 37.175 165 Z") val matrix = Matrix() val size = this.size val scale = minOf(size.width / initialSVG_Width, size.height / initialSVG_Height) matrix.setScale(scale, scale) path.transform(matrix) val composePath = path.asComposePath() if (composePath.contains(offset)) { Toast.makeText(context, "Click!", Toast.LENGTH_SHORT).show() } } }, )
3. 检查路径完整性
确保路径已通过close()正确闭合,且不存在自相交的曲线/直线,这是导致边框不规则的常见原因。
内容的提问来源于stack exchange,提问作者pierre.b
相关产品推荐
相关产品推荐

