常见模糊处理用例

以下部分介绍了如何将 Modifier.blur、BlurRadiusSpec、BlurStop 和 BlurScope 组合用于常见的界面模式。如需了解这些 API,请参阅在 Compose 中实现模糊效果。

模糊滚动或可拖动内容的边缘

在显示滚动列表、轮播界面或选择器时,您可以使用多停靠点渐进式模糊处理来柔化容器边缘附近的项,同时使活跃的中心区域保持清晰对焦。

淡化滚动列表的顶部和底部边缘

如需在列表项滚动进入和滚出垂直列表的视口时柔化它们,请将具有 BlurStop 条目的 BlurRadiusSpec.verticalGradient 应用于 LazyColumn。在以下示例中,歌词会逐行滚动,0.dp 停止点位于 0.42f 和 0.58f 之间,使活动行在中心保持清晰,而靠近顶部和底部边缘的行则逐渐模糊:

@Composable
fun SongListTopBottomBlurExample(lyrics: List<String>) {
    // ...
    LazyColumn(
        state = listState,
        flingBehavior = flingBehavior,
        modifier = Modifier
            .width(330.dp)
            .height(480.dp)
            .padding(horizontal = 24.dp)
            .blur(
                blurRadiusSpec = BlurRadiusSpec.verticalGradient(
                    stops = listOf(
                        BlurStop(fraction = 0.00f, radius = 18.dp), // Heavily blurred top edge
                        BlurStop(fraction = 0.24f, radius = 8.dp), // Soft upper transition
                        BlurStop(fraction = 0.42f, radius = 0.dp), // Sharp center focus starts
                        BlurStop(fraction = 0.58f, radius = 0.dp), // Sharp center focus ends
                        BlurStop(fraction = 0.76f, radius = 8.dp), // Soft lower transition
                        BlurStop(fraction = 1.00f, radius = 18.dp), // Heavily blurred bottom edge
                    ),
                ),
                edgeTreatment = BlurredEdgeTreatment.Unbounded,
            ),
        contentPadding = PaddingValues(vertical = 36.dp),
        verticalArrangement = Arrangement.spacedBy(26.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
    ) {
        // ...
    }
}

图 1. 一个垂直排列的歌词文本行列表,其中顶部和底部的行模糊淡出,而中间的行清晰可见。

淡化水平控件的左右边缘

同样,如需将焦点吸引到可水平滚动或可拖动的组件(例如滚轮选择器、轮播界面或测量刻度)的中心,请应用具有 BlurStop 条目的 BlurRadiusSpec.horizontalGradient。在以下可拖动的体重秤中,左右边缘会柔化,而中心指示针附近的刻度和数字则保持完全清晰:

@Composable
fun HorizontalScaleBlurExample() {
    var selectedValue by remember { mutableFloatStateOf(65.0f) }
    val animatedValue by animateFloatAsState(
        targetValue = selectedValue,
        animationSpec = tween(durationMillis = 900, easing = FastOutSlowInEasing),
        label = "horizontalScaleSmoothScroll",
    )
    Column(
        modifier = Modifier
            .width(340.dp)
            .background(Color(0xFF0F172A), RoundedCornerShape(24.dp))
            .padding(vertical = 24.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
    ) {
        Text(
            text = "${(animatedValue * 10).roundToInt() / 10.0} kg",
            color = Color.White,
            fontSize = 44.sp,
            fontWeight = FontWeight.Black,
        )
        Spacer(Modifier.height(16.dp))
        Box(
            // ...
        ) {
            // Ruler ticks blurred at the left and right edges, sharp at center
            Canvas(
                modifier = Modifier
                    .fillMaxSize()
                    .blur(
                        blurRadiusSpec = BlurRadiusSpec.horizontalGradient(
                            stops = listOf(
                                BlurStop(fraction = 0.00f, radius = 14.dp),
                                BlurStop(fraction = 0.28f, radius = 0.dp),
                                BlurStop(fraction = 0.72f, radius = 0.dp),
                                BlurStop(fraction = 1.00f, radius = 14.dp),
                            ),
                        ),
                        edgeTreatment = BlurredEdgeTreatment.Unbounded,
                    ),
            ) {
                // Draw horizontal ruler ticks offset by `animatedValue`
                // ...
            }
            // ...
        }
    }
}

图 2. 一个水平标尺刻度微件,边缘附近的刻度线模糊,指针附近的刻度线清晰。

使用 linearGradient 创建对角线模糊效果

BlurRadiusSpec.linearGradient 会在图层坐标空间中的 start 和 end DpOffset 值之间的线段上改变模糊半径。在 Modifier.blur lambda 块内,您可以读取 BlurScope.size,以在主打图片上从左上角到右下角运行对角线渐进式模糊处理,使左上角的主体保持完全清晰,同时逐渐模糊叠加文本后面的右下角区域:

@Composable
fun DiagonalLinearBlurCardExample() {
    Box(
        modifier = Modifier
            .wrapContentSize()
            .background(Color(0xFFF8FAFC))
            .padding(10.dp),
    ) {
        Image(
            painter = painterResource(id = R.drawable.dog),
            contentDescription = stringResource(id = R.string.dog_content_description),
            contentScale = ContentScale.Crop,
            modifier = Modifier
                .size(width = 300.dp, height = 380.dp)
                .blur {
                    blurRadiusSpec = BlurRadiusSpec.linearGradient(
                        start = DpOffset(0.dp, 0.dp),
                        end = DpOffset(size.width, size.height),
                        stops = listOf(
                            BlurStop(fraction = 0.00f, radius = 0.dp),
                            BlurStop(fraction = 0.50f, radius = 0.dp),
                            BlurStop(fraction = 0.75f, radius = 14.dp),
                            BlurStop(fraction = 1.00f, radius = 28.dp),
                        ),
                    )
                    edgeTreatment = BlurredEdgeTreatment(RoundedCornerShape(24.dp))
                },
        )
        // ...
    }
}

一张狗狗肖像,左上角清晰,向右下角逐渐模糊,模糊区域上叠加了清晰的白色标题和副标题文字。
图 3. 一张狗狗肖像,左上角清晰,向右下角逐渐模糊,模糊区域上叠加了清晰的白色标题和副标题文字。

使用 radialGradient 创建焦点聚光灯

BlurRadiusSpec.radialGradient 通过从焦点向外改变模糊半径来创建圆形聚光灯效果。将 center 定义为 DpOffset,将 fallOffRadius 定义为达到 endRadius 时与 center 的 Dp 距离。如果您将 center 保留为 DpOffset.Unspecified,则渐变会以可组合项为中心;如果您将 fallOffRadius 保留为 Dp.Unspecified,则默认值为图层较小尺寸的一半。

例如,在一段文本上应用 BlurRadiusSpec.radialGradient 并根据指针拖动事件更新 center,可让用户在段落中拖动圆形阅读聚光灯,同时模糊周围的文本,如以下示例所示:

@Composable
fun RadialSpotlightTextExample() {
    var touchOffset by remember { mutableStateOf(Offset.Unspecified) }
    Box(
        modifier = Modifier
            .size(width = 330.dp, height = 340.dp)
            .background(Color(0xFF0F172A), RoundedCornerShape(20.dp))
            .pointerInput(Unit) {
                detectDragGestures(
                    onDragStart = { offset -> touchOffset = offset },
                    onDrag = { change, _ -> touchOffset = change.position },
                )
            }
            .blur {
                val spotlightCenter = if (touchOffset.isSpecified) {
                    DpOffset(touchOffset.x.toDp(), touchOffset.y.toDp())
                } else {
                    DpOffset(x = size.width / 2, y = size.height / 2)
                }
                blurRadiusSpec = BlurRadiusSpec.radialGradient(
                    startRadius = 0.dp,
                    endRadius = 10.dp,
                    center = spotlightCenter,
                    fallOffRadius = 215.dp,
                )
                edgeTreatment = BlurredEdgeTreatment(RoundedCornerShape(20.dp))
            }
            .padding(22.dp),
    ) {
        // ...
    }
}

图 4. 一段文字,其中指针周围有一个圆形区域,区域内的文字清晰,越靠近圆形边缘,文字越模糊。

使用着色器创建自定义模糊遮罩

对于超出线性或径向渐变的复杂空间模糊分布(例如重复图案、任意形状或程序化噪声),请将自定义 Shader(例如 AGSL RuntimeShader)传递给 BlurRadiusSpec.shader。着色器会根据左上角原点的图层像素坐标进行求值,其采样的 Alpha 通道 (0..1) 会缩放 maxRadius。

使用 remember 可组合函数创建着色器和 BlurRadiusSpec.shader 配置一次,以便在重组过程中重复使用单个实例。Compose 会在每次解析时调用具有图层大小的配置块,您可以在其中设置与大小相关的 uniform:

@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@Composable
fun ShaderBlurExample() {
    // Creates 6 diagonal frosted-glass bands across the image.
    val maskShader = remember {
        RuntimeShader(
            """
            uniform float2 size;
            half4 main(float2 coord) {
                float2 uv = coord / size;
                float band = smoothstep(0.4, 0.6, fract((uv.x + uv.y) * 3.0));
                return half4(0.0, 0.0, 0.0, half(band));
            }
            """.trimIndent(),
        )
    }
    val maskBlurSpec = remember(maskShader) {
        BlurRadiusSpec.shader(maxRadius = 24.dp) { sizePx ->
            maskShader.setFloatUniform("size", sizePx.width, sizePx.height)
            maskShader
        }
    }
    Image(
        painter = painterResource(id = R.drawable.dog),
        contentDescription = stringResource(id = R.string.dog_content_description),
        contentScale = ContentScale.Crop,
        modifier = Modifier
            .size(150.dp)
            .blur(blurRadiusSpec = maskBlurSpec),
    )
}

一张狗狗图片,上面有由自定义着色器遮罩驱动的对角线磨砂玻璃模糊带。
图 5. 一张狗狗图片,其中有由自定义着色器遮罩驱动的对角线磨砂玻璃模糊带。