Các trường hợp làm mờ phổ biến

Các phần sau đây cho biết cách kết hợp Modifier.blur, BlurRadiusSpec, BlurStop và BlurScope cho các mẫu giao diện người dùng phổ biến. Để biết thông tin giới thiệu về các API này, hãy xem bài viết Làm mờ trong Compose.

Làm mờ các cạnh của nội dung có thể cuộn hoặc kéo

Khi hiển thị danh sách cuộn, băng chuyền hoặc bộ chọn, bạn có thể sử dụng hiệu ứng làm mờ tăng dần nhiều điểm dừng để làm mờ các mục gần cạnh vùng chứa trong khi vẫn giữ cho vùng trung tâm hoạt động được lấy nét rõ ràng.

Làm mờ các cạnh trên và dưới của danh sách cuộn

Để làm mờ các mục khi chúng cuộn vào và ra khỏi khung nhìn của danh sách dọc, hãy áp dụng BlurRadiusSpec.verticalGradient với các mục BlurStop cho LazyColumn. Trong ví dụ sau đây, khi cuộn lời bài hát theo từng dòng, các điểm dừng 0.dp giữa 0.42f và 0.58f sẽ giữ cho dòng đang hoạt động ở giữa luôn sắc nét, trong khi các dòng về phía cạnh trên và dưới sẽ mờ dần:

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

Hình 1. Danh sách dọc gồm các dòng lời bài hát, trong đó các dòng trên cùng và dưới cùng mờ dần trong khi dòng giữa thì rõ ràng.

Làm mờ cạnh trái và phải của một thành phần điều khiển ngang

Tương tự, để thu hút sự chú ý vào giữa một thành phần có thể cuộn hoặc kéo được theo chiều ngang, chẳng hạn như bộ chọn bánh xe, băng chuyền hoặc thang đo, hãy áp dụng BlurRadiusSpec.horizontalGradient với các mục BlurStop. Trong cân trọng lượng có thể kéo sau đây, các cạnh bên trái và bên phải sẽ mềm đi trong khi các vạch và số gần kim chỉ báo ở giữa vẫn hoàn toàn sắc nét:

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

Hình 2. Một tiện ích thước đo ngang hiển thị các vạch mờ gần các cạnh và vạch rõ gần kim chỉ báo.

Tạo hiệu ứng làm mờ đường chéo bằng linearGradient

BlurRadiusSpec.linearGradient thay đổi bán kính làm mờ dọc theo một đoạn thẳng giữa các giá trị start và end DpOffset trong không gian toạ độ của lớp. Bên trong khối lambda Modifier.blur, bạn có thể đọc BlurScope.size để chạy hiệu ứng làm mờ tăng dần theo đường chéo trên hình ảnh chính từ góc trên cùng bên trái đến góc dưới cùng bên phải, giữ cho chủ thể ở trên cùng bên trái hoàn toàn sắc nét trong khi làm mờ dần vùng dưới cùng bên phải phía sau văn bản phủ lên:

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

Ảnh chân dung một chú chó sắc nét ở phía trên bên trái và mờ dần về phía góc dưới bên phải, với văn bản tiêu đề và phụ đề màu trắng sắc nét phủ lên vùng bị mờ.
Hình 3. Ảnh chân dung một chú chó, sắc nét ở góc trên bên trái và mờ dần về phía góc dưới bên phải, có tiêu đề và phụ đề màu trắng sắc nét phủ lên vùng bị mờ.

Tạo tiêu điểm nổi bật bằng radialGradient

BlurRadiusSpec.radialGradient tạo hiệu ứng tiêu điểm hình tròn bằng cách thay đổi bán kính làm mờ ra bên ngoài từ một tiêu điểm. Xác định center là DpOffset và fallOffRadius là khoảng cách Dp từ center mà tại đó đạt được endRadius. Nếu bạn để center là DpOffset.Unspecified, thì dải chuyển màu sẽ nằm ở giữa composable và nếu bạn để fallOffRadius là Dp.Unspecified, thì dải chuyển màu sẽ mặc định là một nửa kích thước nhỏ hơn của lớp.

Ví dụ: việc áp dụng BlurRadiusSpec.radialGradient cho một đoạn văn bản và cập nhật center từ các sự kiện kéo con trỏ cho phép người dùng kéo đèn đọc sách hình tròn qua đoạn văn bản trong khi làm mờ văn bản xung quanh, như trong ví dụ sau:

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

Hình 4. Một đoạn văn bản có vùng văn bản sắc nét hình tròn xung quanh con trỏ, dần mờ đi về phía rìa của hình tròn.

Tạo mặt nạ làm mờ tuỳ chỉnh bằng chương trình đổ bóng

Đối với các phân phối làm mờ không gian phức tạp ngoài dải chuyển màu tuyến tính hoặc dải chuyển màu toả tròn, chẳng hạn như mẫu lặp lại, hình dạng tuỳ ý hoặc nhiễu thủ tục, hãy truyền Shader tuỳ chỉnh, chẳng hạn như AGSL RuntimeShader, đến BlurRadiusSpec.shader. Chương trình đổ bóng đánh giá theo toạ độ pixel của lớp từ gốc trên cùng bên trái và kênh alpha được lấy mẫu (0..1) sẽ điều chỉnh tỷ lệ maxRadius.

Tạo cấu hình chương trình đổ bóng và BlurRadiusSpec.shader một lần bằng cách sử dụng hàm composable remember để một thực thể duy nhất được sử dụng lại trong các recomposition. Compose gọi khối cấu hình có kích thước lớp trên mỗi lần phân giải, đây là nơi bạn đặt các biến đồng nhất phụ thuộc vào kích thước:

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

Hình ảnh một chú chó có các dải làm mờ bằng kính mờ theo đường chéo do mặt nạ chương trình đổ bóng tuỳ chỉnh điều khiển.
Hình 5. Hình ảnh chú chó có các dải làm mờ bằng kính mờ đường chéo được điều khiển bằng mặt nạ chương trình đổ bóng tuỳ chỉnh.