Typowe przypadki użycia rozmycia

W sekcjach poniżej pokazujemy, jak połączyć Modifier.blur, BlurRadiusSpec, BlurStop i BlurScope w typowych wzorcach interfejsu. Wprowadzenie do tych interfejsów API znajdziesz w artykule Rozmycie w Compose.

Rozmycie krawędzi przewijanych lub przeciąganych treści

Podczas wyświetlania przewijanej listy, karuzeli lub selektora możesz użyć wielostopniowego rozmycia progresywnego, aby zmiękczyć elementy w pobliżu krawędzi kontenera, zachowując jednocześnie ostrość aktywnego obszaru centralnego.

Zanikanie górnej i dolnej krawędzi przewijanej listy

Aby złagodzić efekt przewijania elementów na liście pionowej, zastosuj BlurRadiusSpec.verticalGradient z wpisami BlurStop do LazyColumn. W tym przykładzie, w którym tekst piosenki jest przewijany wiersz po wierszu, punkty 0.dp między 0.42f i 0.58f utrzymują ostrość aktywnego wiersza na środku, podczas gdy wiersze u góry i u dołu stopniowo się rozmywają:

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

Rysunek 1. Pionowa lista wierszy tekstu utworu, w której górne i dolne wiersze są rozmyte, a środkowy wiersz jest wyraźny.

Zanikanie lewej i prawej krawędzi elementu sterującego w poziomie

Podobnie, aby skupić uwagę na środku komponentu, który można przewijać lub przeciągać w poziomie, takiego jak selektor kołowy, karuzela lub skala pomiarowa, zastosuj BlurRadiusSpec.horizontalGradient z wpisami BlurStop. Na poniższej skali wagi, którą można przeciągać, lewa i prawa krawędź są rozmyte, a kreski i cyfry w pobliżu środkowej wskazówki są całkowicie ostre:

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

Rysunek 2. Widżet poziomej linijki z rozmytymi kreskami przy krawędziach i wyraźnymi kreskami przy wskazówce.

Utwórz rozmycie po przekątnej za pomocą linearGradient

BlurRadiusSpec.linearGradient zmienia promień rozmycia wzdłuż odcinka linii między wartościami start i end DpOffset w przestrzeni współrzędnych warstwy. Wewnątrz bloku lambda Modifier.blur możesz odczytać BlurScope.size, aby uruchomić progresywne rozmycie po przekątnej banera powitalnego od lewego górnego do prawego dolnego rogu, zachowując pełną ostrość obiektu w lewym górnym rogu i stopniowo rozmywając prawy dolny obszar za nałożonym tekstem:

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

Portret psa, który jest ostry w lewym górnym rogu i stopniowo rozmywa się w prawym dolnym rogu, z ostrym białym tytułem i podtytułem nałożonym na rozmyty obszar.
Rysunek 3. Portret psa, który jest ostry w lewym górnym rogu i stopniowo rozmywa się w prawym dolnym rogu, z ostrym białym tytułem i podtytułem nałożonym na rozmyty obszar.

Tworzenie wyróżnienia z użyciem radialGradient

BlurRadiusSpec.radialGradient tworzy efekt okrągłego reflektora, zmieniając promień rozmycia na zewnątrz od punktu ostrości. Zdefiniuj center jako DpOffset, a fallOffRadius jako odległość Dp od center, przy której osiągana jest wartość endRadius. Jeśli pozostawisz center jako DpOffset.Unspecified, gradient będzie wyśrodkowany na elemencie kompozycyjnym, a jeśli pozostawisz fallOffRadius jako Dp.Unspecified, domyślnie będzie to połowa mniejszego wymiaru warstwy.

Na przykład zastosowanie BlurRadiusSpec.radialGradient do fragmentu tekstu i aktualizowanie center na podstawie zdarzeń przeciągania wskaźnika pozwala użytkownikowi przeciągać okrągłe podświetlenie czytanego tekstu po akapicie, rozmywając otaczający tekst, jak w tym przykładzie:

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

Rysunek 4. Akapit tekstu z okrągłym obszarem ostrego tekstu wokół wskaźnika, który staje się coraz bardziej rozmyty w kierunku krawędzi okręgu.

Tworzenie niestandardowej maski rozmycia za pomocą cieniowania

W przypadku złożonych przestrzennych rozkładów rozmycia, które wykraczają poza gradienty liniowe lub gradienty promieniowe, takich jak powtarzające się wzory, dowolne kształty lub szum proceduralny, przekaż niestandardowy obiekt Shader, np. AGSL RuntimeShader, do BlurRadiusSpec.shader. Program do cieniowania jest oceniany we współrzędnych pikseli warstwy od lewego górnego rogu, a jego próbkowany kanał alfa (0..1) skaluje maxRadius.

Utwórz cieniowanie i konfigurację BlurRadiusSpec.shader raz za pomocą funkcji typu „composable” remember, aby pojedyncza instancja była używana wielokrotnie w różnych rekompozycjach. Compose wywołuje blok konfiguracji z rozmiarem warstwy przy każdym rozwiązaniu, gdzie ustawiasz zmienne uniform zależne od rozmiaru:

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

Obraz psa z ukośnymi pasmami rozmycia przypominającego oszronione szkło, które są generowane przez niestandardową maskę cieniowania.
Rysunek 5. Obraz psa z ukośnymi pasami rozmycia przypominającego oszronione szkło, które są generowane przez niestandardową maskę cieniowania.