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

Android 10设备中Jetpack Compose绘制的Squircle Image偶现消失问题

Android 10下Jetpack Compose Squircle Image切换页面后偶发消失问题

我在Android Jetpack Compose中实现Squircle形状图片时遇到偶发异常:仅在Android 10设备上,反复切换页面后Squircle Image会无征兆消失,无异常日志抛出,出现概率约1/10。

正常显示效果:
正常显示的Squircle Image

消失后效果:
消失后的Squircle Image

使用的实现代码

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 22:06:28