Android 10设备中Jetpack Compose绘制的Squircle Image偶现消失问题
Android 10下Jetpack Compose Squircle Image切换页面后偶发消失问题
我在Android Jetpack Compose中实现Squircle形状图片时遇到偶发异常:仅在Android 10设备上,反复切换页面后Squircle Image会无征兆消失,无异常日志抛出,出现概率约1/10。
正常显示效果:
消失后效果:
使用的实现代码
SquircleShape 实现
class SquircleShape : Shape { override fun createOutline( size: Size, layoutDirection: LayoutDirection, density: Density ) = Outline.Generic( path = createSquirclePath(size, SMOOTHING) ) private fun createSquirclePath(size: Size, smoothing: Double): androidx.compose.ui.graphics.Path { return Path().apply { val oversize = size.width * OVERSAMPLING_MULTIPLIER val squircleRadius = (oversize / 2F).toInt() // power radius before for optimization val poweredRadius = squircleRadius .toDouble() .pow(smoothing) // generate Y coordinates for path val yCoordinates = (-squircleRadius..squircleRadius).map { x -> x.toFloat() to evalSquircleFun(x, poweredRadius, smoothing) } // generate Y coordinates for mirror half of squircle shape val yMirroredCoordinates = yCoordinates.map { (x, y) -> Pair(x, -y) } var currentX = 0F var currentY = 0F // set path by using quadraticBezier (yCoordinates + yMirroredCoordinates).forEach { (x, y) -> quadTo(currentX, currentY, x, y) currentX = x currentY = y } close() // scale down to original size - for better corners without anti-alias transform( scaleMatrix( sx = 1 / OVERSAMPLING_MULTIPLIER, sy = 1 / OVERSAMPLING_MULTIPLIER ) ) // translate path to center transform( translationMatrix( tx = size.width / 2, ty = size.height / 2 ) ) }.asComposePath() } // squircle formula: | (r^smoothing) - |x|^5 | ^ (1 / smoothing) private fun evalSquircleFun(x: Int, poweredRadius: Double, smoothing: Double) = (poweredRadius - abs(x.toDouble().pow(smoothing))).pow(1 / smoothing).toFloat() companion object { private const val SMOOTHING = 3.0 private const val OVERSAMPLING_MULTIPLIER = 4F } }
SquircleImage 实现
@Composable fun SquircleImage( modifier: Modifier = Modifier, imageRequest: ImageRequest, size: Dp, backgroundColor: Color = Gray10, borderColor: Color = Gray20, borderSize: Dp = 0.5.dp, radius: Dp = 24.dp ) { Box( modifier = modifier .size(size) .clip(SquircleShape()) .background(borderColor), contentAlignment = Alignment.Center ) { Image( painter = rememberAsyncImagePainter( model = imageRequest, contentScale = ContentScale.Crop ), contentDescription = null, contentScale = ContentScale.Crop, modifier = Modifier .clip(SquircleShape()) .background(backgroundColor) .size(size - borderSize - borderSize), ) } }
可能的解决方案
1. 缓存SquirclePath避免重复创建
原SquircleShape每次调用createOutline都会重新生成Path,在Android 10的Compose渲染机制下可能触发异常,通过缓存Path优化:
class SquircleShape : Shape { private val pathCache = mutableMapOf<Size, androidx.compose.ui.graphics.Path>() override fun createOutline( size: Size, layoutDirection: LayoutDirection, density: Density ): Outline { val path = pathCache.getOrPut(size) { createSquirclePath(size, SMOOTHING) } return Outline.Generic(path = path) } // 其余代码保持不变 }
2. 替换手动创建Painter为官方AsyncImage
直接使用Coil提供的AsyncImage组件,它内部已处理Painter的缓存和生命周期管理,稳定性更高:
@Composable fun SquircleImage( modifier: Modifier = Modifier, imageRequest: ImageRequest, size: Dp, backgroundColor: Color = Gray10, borderColor: Color = Gray20, borderSize: Dp = 0.5.dp, radius: Dp = 24.dp ) { Box( modifier = modifier .size(size) .clip(SquircleShape()) .background(borderColor), contentAlignment = Alignment.Center ) { AsyncImage( model = imageRequest, contentDescription = null, contentScale = ContentScale.Crop, modifier = Modifier .clip(SquircleShape()) .background(backgroundColor) .size(size - borderSize * 2), ) } }
3. 避免嵌套Clip,改用drawWithCache统一绘制
嵌套的clip可能在Android 10的Compose渲染管线中触发异常,改用drawWithCache统一绘制形状、背景和边框:
@Composable fun SquircleImage( modifier: Modifier = Modifier, imageRequest: ImageRequest, size: Dp, backgroundColor: Color = Gray10, borderColor: Color = Gray20, borderSize: Dp = 0.5.dp, radius: Dp = 24.dp ) { val borderSizePx = borderSize.toPx() val squircleShape = SquircleShape() AsyncImage( model = imageRequest, contentDescription = null, contentScale = ContentScale.Crop, modifier = modifier .size(size) .drawWithCache { val outline = squircleShape.createOutline( size = size.toSize(), layoutDirection = LayoutDirection.Ltr, density = this ) val squirclePath = (outline as Outline.Generic).path onDrawWithContent { drawPath(squirclePath, color = backgroundColor) drawContent() drawPath( squirclePath, color = borderColor, style = Stroke(width = borderSizePx) ) } } ) }
4. 升级/降级Compose版本
如果使用的是较旧的Compose版本,Android 10上可能存在渲染bug,尝试升级到最新稳定版(如Compose BOM 2024.04.00),或降级到已知稳定的版本。
内容的提问来源于stack exchange,提问作者dev.farmer
相关产品推荐
相关产品推荐

