Casi d'uso comuni della sfocatura

Le sezioni seguenti mostrano come combinare Modifier.blur, BlurRadiusSpec, BlurStop e BlurScope per i pattern UI comuni. Per un'introduzione a queste API, consulta Sfocatura in Compose.

Sfoca i bordi dei contenuti scorrevoli o trascinabili

Quando mostri un elenco a scorrimento, un carosello o un selettore, puoi utilizzare sfocature progressive con più punti di interruzione per ammorbidire gli elementi vicino ai bordi del container, mantenendo al contempo la regione centrale attiva ben a fuoco.

Dissolvenza dei bordi superiore e inferiore di un elenco a scorrimento

Per ammorbidire gli elementi quando scorrono dentro e fuori dall'area visibile di un elenco verticale, applica BlurRadiusSpec.verticalGradient con voci BlurStop a LazyColumn. Nell'esempio seguente, che scorre il testo di un brano riga per riga, i punti di interruzione 0.dp tra 0.42f e 0.58f mantengono la riga attiva al centro nitida, mentre le righe verso i bordi superiore e inferiore sfocano progressivamente:

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

Figura 1. Un elenco verticale di righe di testo dei testi delle canzoni in cui le righe superiore e inferiore sfumano in una sfocatura, mentre la riga centrale è nitida.

Sfuma i bordi sinistro e destro di un controllo orizzontale

Allo stesso modo, per attirare l'attenzione sul centro di un componente scorrevole o trascinabile in orizzontale, come un selettore a rotella, un carosello o una scala di misurazione, applica BlurRadiusSpec.horizontalGradient con voci BlurStop. Nella seguente bilancia trascinabile, i bordi sinistro e destro sono sfocati, mentre i segni di graduazione e i numeri vicino alla lancetta dell'indicatore centrale rimangono completamente nitidi:

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

Figura 2. Un widget con un righello orizzontale che mostra tacche sfocate vicino ai bordi e tacche nitide vicino all'indicatore.

Crea una sfocatura diagonale con linearGradient

BlurRadiusSpec.linearGradient varia il raggio di sfocatura lungo un segmento di linea tra i valori start e end DpOffset nello spazio delle coordinate del livello. All'interno del blocco lambda Modifier.blur, puoi leggere BlurScope.size per eseguire una sfocatura progressiva diagonale su un'immagine hero dall'angolo in alto a sinistra all'angolo in basso a destra, mantenendo il soggetto in alto a sinistra completamente nitido e sfocando progressivamente la regione in basso a destra dietro il testo sovrapposto:

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

Un ritratto di un cane nitido in alto a sinistra e progressivamente sfocato verso l'angolo in basso a destra, con un testo bianco nitido per il titolo e il sottotitolo sovrapposto alla regione sfocata.
Figura 3. Il ritratto di un cane nitido in alto a sinistra e progressivamente sfocato verso l'angolo in basso a destra, con un testo bianco nitido per il titolo e il sottotitolo sovrapposto alla regione sfocata.

Crea un momento di concentrazione con radialGradient

BlurRadiusSpec.radialGradient crea un effetto di luce circolare variando il raggio di sfocatura verso l'esterno da un punto focale. Definisci center come DpOffset e fallOffRadius come distanza Dp da center alla quale viene raggiunto endRadius. Se lasci center come DpOffset.Unspecified, il gradiente è centrato sul composable, mentre se lasci fallOffRadius come Dp.Unspecified, il valore predefinito è la metà della dimensione più piccola del livello.

Ad esempio, l'applicazione di BlurRadiusSpec.radialGradient su un passaggio di testo e l'aggiornamento di center dagli eventi di trascinamento del puntatore consentono all'utente di trascinare un riflettore di lettura circolare sul paragrafo sfocando il testo circostante, come mostrato nell'esempio seguente:

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

Figura 4. Un paragrafo di testo con un'area circolare di testo nitido attorno al puntatore che diventa progressivamente sfocato verso il bordo del cerchio.

Crea una maschera di sfocatura personalizzata con uno shader

Per distribuzioni di sfocatura spaziale complesse che vanno oltre i gradienti lineari o radiali, come pattern ripetuti, forme arbitrarie o rumore procedurale, passa un Shader personalizzato, come un AGSL RuntimeShader, a BlurRadiusSpec.shader. Lo shader viene valutato nelle coordinate dei pixel del livello dall'origine in alto a sinistra e il suo canale alfa campionato (0..1) scala maxRadius.

Crea lo shader e la configurazione BlurRadiusSpec.shader una volta utilizzando la funzione componibile remember, in modo che una singola istanza venga riutilizzata nelle ricomposizioni. Compose richiama il blocco di configurazione con le dimensioni del livello a ogni risoluzione, ovvero dove imposti le uniformi dipendenti dalle dimensioni:

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

Un'immagine di un cane con bande diagonali di sfocatura effetto vetro smerigliato generate da una maschera di shader personalizzata.
Figura 5. Un'immagine di un cane con bande diagonali di sfocatura effetto vetro smerigliato generate da una maschera di shader personalizzata.