Cas d'utilisation courants du floutage

Les sections suivantes expliquent comment combiner Modifier.blur, BlurRadiusSpec, BlurStop et BlurScope pour les modèles d'UI courants. Pour en savoir plus sur ces API, consultez Floutage dans Compose.

Flouter les bords du contenu défilant ou déplaçable

Lorsque vous affichez une liste déroulante, un carrousel ou un sélecteur, vous pouvez utiliser des flous progressifs à plusieurs arrêts pour adoucir les éléments situés près des bords du conteneur, tout en gardant la région centrale active bien nette.

Estomper les bords supérieur et inférieur d'une liste déroulante

Pour adoucir les éléments lorsqu'ils défilent dans et hors de la fenêtre d'affichage d'une liste verticale, appliquez BlurRadiusSpec.verticalGradient avec des entrées BlurStop à LazyColumn. Dans l'exemple suivant, qui fait défiler les paroles d'une chanson ligne par ligne, les points d'arrêt 0.dp entre 0.42f et 0.58f maintiennent la ligne active au centre, tandis que les lignes vers les bords supérieur et inférieur s'estompent progressivement :

@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,
    ) {
        // ...
    }
}

Figure 1. Liste verticale de lignes de texte de paroles de chanson, où les lignes du haut et du bas sont floutées, tandis que la ligne du milieu est nette.

Estomper les bords gauche et droit d'une commande horizontale

De même, pour attirer l'attention sur le centre d'un composant défilant ou déplaçable horizontalement, comme un sélecteur de roue, un carrousel ou une échelle de mesure, appliquez BlurRadiusSpec.horizontalGradient avec des entrées BlurStop. Dans l'échelle de poids déplaçable suivante, les bords gauche et droit sont adoucis, tandis que les graduations et les chiffres situés près de l'aiguille de l'indicateur central restent parfaitement nets :

@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`
                // ...
            }
            // ...
        }
    }
}

Figure 2. Widget de règle horizontale affichant des graduations floues près des bords et des graduations nettes près de l'aiguille de l'indicateur.

Créer un flou diagonal avec linearGradient

BlurRadiusSpec.linearGradient fait varier le rayon de flou le long d'un segment de ligne entre les valeurs start et end DpOffset dans l'espace de coordonnées du calque. Dans le bloc lambda Modifier.blur, vous pouvez lire BlurScope.size pour exécuter un flou progressif diagonal sur une image héros, de l'angle supérieur gauche à l'angle inférieur droit, en gardant le sujet en haut à gauche complètement net tout en floutant progressivement la zone inférieure droite derrière le texte superposé :

@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))
                },
        )
        // ...
    }
}

Portrait d'un chien net en haut à gauche et progressivement flou vers le coin inférieur droit, avec un titre et un sous-titre blancs nets superposés sur la zone floue.
Figure 3. Portrait d'un chien net en haut à gauche et progressivement flou vers le coin inférieur droit, avec un titre et un sous-titre blancs nets superposés sur la zone floue.

Créer un spotlight de mise au point avec radialGradient

BlurRadiusSpec.radialGradient crée un effet de projecteur circulaire en faisant varier le rayon de flou vers l'extérieur à partir d'un point focal. Définissez center comme DpOffset et fallOffRadius comme la distance Dp par rapport à center à laquelle endRadius est atteint. Si vous laissez center sur DpOffset.Unspecified, le dégradé est centré sur le composable. Si vous laissez fallOffRadius sur Dp.Unspecified, la valeur par défaut est la moitié de la plus petite dimension du calque.

Par exemple, l'application de BlurRadiusSpec.radialGradient sur un passage de texte et la mise à jour de center à partir d'événements de déplacement du pointeur permettent à l'utilisateur de faire glisser un projecteur de lecture circulaire sur le paragraphe tout en floutant le texte environnant, comme illustré dans l'exemple suivant :

@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),
    ) {
        // ...
    }
}

Figure 4. Un paragraphe de texte avec une zone circulaire de texte net autour du pointeur qui devient progressivement flou vers le bord du cercle.

Créer un masque de flou personnalisé avec un nuanceur

Pour les distributions de flou spatial complexes au-delà des dégradés linéaires ou radiaux, comme les motifs répétés, les formes arbitraires ou le bruit procédural, transmettez un Shader personnalisé, tel qu'un RuntimeShader AGSL, à BlurRadiusSpec.shader. Le nuanceur évalue les coordonnées des pixels du calque à partir de l'origine en haut à gauche, et son canal alpha échantillonné (0..1) met à l'échelle maxRadius.

Créez le nuanceur et la configuration BlurRadiusSpec.shader une seule fois à l'aide de la fonction composable remember, afin qu'une seule instance soit réutilisée dans les recompositions. Compose appelle le bloc de configuration avec la taille de la couche à chaque résolution, ce qui vous permet de définir des uniformes dépendant de la taille :

@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),
    )
}

Image d'un chien avec des bandes diagonales floutées (effet verre dépoli) générées par un masque de nuanceur personnalisé.
Figure 5 : Image d'un chien avec des bandes de flou diagonales de type verre dépoli, générées par un masque de nuanceur personnalisé.