Thêm hiệu ứng đổ bóng trong Compose

Đổ bóng giúp nâng cao giao diện người dùng một cách trực quan, cho người dùng biết là có thể tương tác và cung cấp phản hồi ngay lập tức về các hành động của người dùng. Compose cung cấp một số cách để kết hợp bóng vào ứng dụng của bạn:

  • Modifier.shadow(): Tạo bóng dựa trên độ nâng phía sau một thành phần kết hợp tuân theo các nguyên tắc của Material Design.
  • Modifier.dropShadow(): Tạo một bóng có thể tuỳ chỉnh xuất hiện phía sau một thành phần kết hợp, khiến thành phần đó xuất hiện ở vị trí cao hơn.
  • Modifier.innerShadow(): Tạo bóng bên trong đường viền của một thành phần kết hợp, khiến thành phần đó xuất hiện như thể bị ép vào bề mặt phía sau.

Modifier.shadow() phù hợp để tạo bóng cơ bản, trong khi các đối tượng sửa đổi dropShadow()innerShadow() mang đến khả năng kiểm soát chi tiết và độ chính xác cao hơn đối với việc kết xuất bóng.

Trang này mô tả cách triển khai từng đối tượng sửa đổi này, bao gồm cả cách tạo hiệu ứng cho bóng khi người dùng tương tác và cách liên kết các đối tượng sửa đổi innerShadow()dropShadow() để tạo bóng chuyển màu, bóng giả lập 3D và nhiều hiệu ứng khác.

Tạo bóng cơ bản

Modifier.shadow() tạo một bóng cơ bản theo nguyên tắc của Material Design mô phỏng nguồn sáng từ trên cao. Độ sâu bóng dựa trên giá trị elevation và bóng đổ được cắt theo hình dạng của thành phần kết hợp.

@Composable
fun ElevationBasedShadow() {
    Box(
        modifier = Modifier.aspectRatio(1f).fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        Box(
            Modifier
                .size(100.dp, 100.dp)
                .shadow(10.dp, RectangleShape)
                .background(Color.White)
        )
    }
}

Một bóng xám đổ xung quanh hình chữ nhật màu trắng.
Hình 1. Bóng dựa trên độ nâng được tạo bằng Modifier.shadow().

Triển khai bóng đổ

Sử dụng đối tượng sửa đổi dropShadow() để vẽ bóng chính xác phía sau nội dung, giúp phần tử xuất hiện ở vị trí cao hơn.

Bạn có thể kiểm soát các khía cạnh chính sau đây thông qua tham số Shadow:

  • radius: Xác định độ mềm và độ khuếch tán của hiệu ứng làm mờ.
  • color: Xác định màu của sắc thái.
  • offset: Đặt vị trí của hình học bóng dọc theo trục x và y.
  • spread: Kiểm soát độ mở rộng hoặc thu hẹp của hình dạng bóng.

Ngoài ra, tham số shape xác định hình dạng tổng thể của bóng. Bạn có thể sử dụng mọi hình học từ gói androidx.compose.foundation.shape, cũng như các hình dạng biểu cảm của Material.

Để triển khai bóng đổ cơ bản, hãy thêm đối tượng sửa đổi dropShadow() vào chuỗi thành phần kết hợp, cung cấp bán kính, màu sắc và độ lan rộng. Lưu ý rằng nền purpleColor xuất hiện ở trên cùng của bóng sẽ được vẽ sau công cụ sửa đổi dropShadow():

@Composable
fun SimpleDropShadowUsage() {
    Box(Modifier.fillMaxSize()) {
        Box(
            Modifier
                .width(300.dp)
                .height(300.dp)
                .dropShadow(
                    shape = RoundedCornerShape(20.dp),
                    shadow = Shadow(
                        radius = 10.dp,
                        spread = 6.dp,
                        color = Color(0x40000000),
                        offset = DpOffset(x = 4.dp, 4.dp)
                    )
                )
                .align(Alignment.Center)
                .background(
                    color = Color.White,
                    shape = RoundedCornerShape(20.dp)
                )
        ) {
            Text(
                "Drop Shadow",
                modifier = Modifier.align(Alignment.Center),
                fontSize = 32.sp
            )
        }
    }
}

Các điểm chính về mã

  • Đối tượng sửa đổi dropShadow() được áp dụng cho Box bên trong. Đổ bóng có các đặc điểm sau:
    • Hình chữ nhật bo tròn (RoundedCornerShape(20.dp))
    • Bán kính làm mờ là 10.dp, giúp các cạnh mềm và khuếch tán
    • Độ lan rộng 6.dp, giúp tăng kích thước của bóng và làm cho bóng lớn hơn hộp tạo ra bóng
    • Giá trị alpha là 0.5f, khiến bóng có độ trong suốt một phần
  • Sau khi bóng được xác định, .Đối tượng sửa đổi background() được áp dụng.
    • Box có màu trắng.
    • Nền được cắt theo hình chữ nhật bo tròn giống như bóng đổ.

Kết quả

Một bóng đổ màu xám được tạo xung quanh một hình chữ nhật màu trắng.
Hình 2. Một bóng đổ được vẽ xung quanh hình dạng.

Triển khai bóng đổ bên trong

Để tạo hiệu ứng nghịch đảo cho dropShadow(), hãy dùng Modifier.innerShadow(). Hiệu ứng này tạo cảm giác rằng một phần tử bị thụt vào hoặc được nhấn vào bề mặt bên dưới.

Thứ tự là yếu tố quan trọng khi tạo bóng đổ bên trong. Hệ số sửa đổi innerShadow() sẽ xuất hiện ở phía trên cùng của nội dung. Để đảm bảo bóng xuất hiện, bạn thường thực hiện các bước sau:

  1. Vẽ nội dung trong nền.
  2. Áp dụng đối tượng sửa đổi innerShadow() để tạo giao diện lõm.

Nếu innerShadow() được đặt trước nền, thì nền sẽ được vẽ lên bóng, che khuất hoàn toàn bóng.

Ví dụ sau đây minh hoạ việc áp dụng innerShadow() trên RoundedCornerShape:

@Composable
fun SimpleInnerShadowUsage() {
    Box(Modifier.fillMaxSize()) {
        Box(
            Modifier
                .width(300.dp)
                .height(200.dp)
                .align(Alignment.Center)
                // note that the background needs to be defined before defining the inner shadow
                .background(
                    color = Color.White,
                    shape = RoundedCornerShape(20.dp)
                )
                .innerShadow(
                    shape = RoundedCornerShape(20.dp),
                    shadow = Shadow(
                        radius = 10.dp,
                        spread = 2.dp,
                        color = Color(0x40000000),
                        offset = DpOffset(x = 6.dp, 7.dp)
                    )
                )

        ) {
            Text(
                "Inner Shadow",
                modifier = Modifier.align(Alignment.Center),
                fontSize = 32.sp
            )
        }
    }
}

Một bóng trong màu xám bên trong một hình chữ nhật màu trắng.
Hình 3. Ứng dụng Modifier.innerShadow() trên hình chữ nhật có góc bo tròn.

Tạo hiệu ứng đổ bóng khi có lượt tương tác của người dùng

Để làm cho bóng đổ phản hồi các hoạt động tương tác của người dùng, bạn có thể tích hợp các thuộc tính bóng đổ với API hoạt ảnh của Compose. Ví dụ: khi người dùng nhấn vào một nút, bóng đổ có thể thay đổi để cung cấp phản hồi trực quan tức thì.

Đoạn mã sau đây tạo hiệu ứng "được nhấn" có bóng đổ (ảo giác rằng bề mặt đang bị đẩy xuống màn hình):

@Composable
fun AnimatedColoredShadows() {
    SnippetsTheme {
        Box(Modifier.fillMaxSize()) {
            val interactionSource = remember { MutableInteractionSource() }
            val isPressed by interactionSource.collectIsPressedAsState()

            // Create transition with pressed state
            val transition = updateTransition(
                targetState = isPressed,
                label = "button_press_transition"
            )

            fun <T> buttonPressAnimation() = tween<T>(
                durationMillis = 400,
                easing = EaseInOut
            )

            // Animate all properties using the transition
            val shadowAlpha by transition.animateFloat(
                label = "shadow_alpha",
                transitionSpec = { buttonPressAnimation() }
            ) { pressed ->
                if (pressed) 0f else 1f
            }
            // ...

            val blueDropShadow by transition.animateColor(
                label = "shadow_color",
                transitionSpec = { buttonPressAnimation() }
            ) { pressed ->
                if (pressed) Color.Transparent else blueDropShadowColor
            }

            // ...

            Box(
                Modifier
                    .clickable(
                        interactionSource, indication = null
                    ) {
                        // ** ...... **//
                    }
                    .width(300.dp)
                    .height(200.dp)
                    .align(Alignment.Center)
                    .dropShadow(
                        shape = RoundedCornerShape(70.dp),
                        shadow = Shadow(
                            radius = 10.dp,
                            spread = 0.dp,
                            color = blueDropShadow,
                            offset = DpOffset(x = 0.dp, -(2).dp),
                            alpha = shadowAlpha
                        )
                    )
                    .dropShadow(
                        shape = RoundedCornerShape(70.dp),
                        shadow = Shadow(
                            radius = 10.dp,
                            spread = 0.dp,
                            color = darkBlueDropShadow,
                            offset = DpOffset(x = 2.dp, 6.dp),
                            alpha = shadowAlpha
                        )
                    )
                    // note that the background needs to be defined before defining the inner shadow
                    .background(
                        color = Color(0xFFFFFFFF),
                        shape = RoundedCornerShape(70.dp)
                    )
                    .innerShadow(
                        shape = RoundedCornerShape(70.dp),
                        shadow = Shadow(
                            radius = 8.dp,
                            spread = 4.dp,
                            color = innerShadowColor2,
                            offset = DpOffset(x = 4.dp, 0.dp)
                        )
                    )
                    .innerShadow(
                        shape = RoundedCornerShape(70.dp),
                        shadow = Shadow(
                            radius = 20.dp,
                            spread = 4.dp,
                            color = innerShadowColor1,
                            offset = DpOffset(x = 4.dp, 0.dp),
                            alpha = innerShadowAlpha
                        )
                    )

            ) {
                Text(
                    "Animated Shadows",
                    // ...
                )
            }
        }
    }
}

Các điểm chính về mã

  • Khai báo trạng thái bắt đầu và trạng thái kết thúc cho các tham số cần tạo ảnh động khi nhấn bằng transition.animateColortransition.animateFloat.
  • Sử dụng updateTransition và cung cấp targetState (targetState = isPressed) đã chọn để xác minh rằng tất cả hiệu ứng chuyển động đều được đồng bộ hoá. Bất cứ khi nào isPressed thay đổi, đối tượng chuyển cảnh sẽ tự động quản lý ảnh động của tất cả các thuộc tính con từ giá trị hiện tại đến giá trị mục tiêu mới.
  • Xác định quy cách buttonPressAnimation, quy cách này kiểm soát thời gian và tốc độ của hiệu ứng chuyển đổi. Nó chỉ định một tween (viết tắt của in-between) với thời lượng 400 mili giây và một đường cong EaseInOut, tức là ảnh động bắt đầu chậm, tăng tốc ở giữa và chậm lại ở cuối.
  • Xác định một Box bằng chuỗi các hàm sửa đổi áp dụng tất cả các thuộc tính động để tạo phần tử trực quan, bao gồm cả những thuộc tính sau:
    • .clickable(): Một đối tượng sửa đổi giúp Box có tính tương tác.
    • .dropShadow(): Hai bóng đổ bên ngoài được áp dụng trước. Các thuộc tính màu và alpha của chúng được liên kết với các giá trị động (blueDropShadow, v.v.) và tạo ra giao diện nổi ban đầu.
    • .innerShadow(): Hai bóng đổ bên trong được vẽ lên trên nền. Các thuộc tính của chúng được liên kết với nhóm giá trị được tạo ảnh động khác (innerShadowColor1, v.v.) và tạo ra giao diện thụt lề.

Kết quả

Hình 4. Một bóng đổ chuyển động khi người dùng nhấn.

Tạo bóng đổ chuyển màu

Đổ bóng không chỉ giới hạn ở màu đồng nhất. API bóng đổ chấp nhận một Brush, cho phép bạn tạo bóng đổ chuyển màu.

Box(
    modifier = Modifier
        .width(240.dp)
        .height(200.dp)
        .dropShadow(
            shape = RoundedCornerShape(70.dp),
            shadow = Shadow(
                radius = 10.dp,
                spread = animatedSpread.dp,
                brush = Brush.sweepGradient(
                    colors
                ),
                offset = DpOffset(x = 0.dp, y = 0.dp),
                alpha = animatedAlpha
            )
        )
        .clip(RoundedCornerShape(70.dp))
        .background(Color(0xEDFFFFFF)),
    contentAlignment = Alignment.Center
) {
    Text(
        text = breathingText,
        color = Color.Black,
        style = MaterialTheme.typography.bodyLarge
    )
}

Các điểm chính về mã

  • dropShadow() thêm bóng phía sau hộp.
  • brush = Brush.sweepGradient(colors) tô bóng bằng hiệu ứng chuyển màu xoay theo danh sách colors được xác định trước, tạo hiệu ứng cầu vồng.

Kết quả

Bạn có thể dùng cọ làm bóng để tạo hiệu ứng chuyển màu dropShadow() có hiệu ứng hoạt hoạ "mờ dần":

Hình 5. Một bóng đổ có hiệu ứng chuyển màu.

Kết hợp bóng

Bạn có thể kết hợp và xếp lớp các hệ số sửa đổi dropShadow()innerShadow() để tạo nhiều hiệu ứng. Các phần sau đây cho bạn biết cách tạo bóng giả lập, bóng tân thô mộc và bóng thực tế bằng kỹ thuật này.

Tạo bóng đổ theo phong cách tân hoạ

Đổ bóng theo phong cách tân hình học có đặc điểm là xuất hiện một cách tự nhiên từ nền với vẻ ngoài mềm mại. Để tạo bóng đổ nổi, hãy làm như sau:

  1. Sử dụng một phần tử có cùng màu với nền.
  2. Áp dụng hai bóng đổ mờ đối lập: một bóng sáng cho một góc và một bóng tối cho góc đối diện.

Đoạn mã sau đây xếp lớp 2 đối tượng sửa đổi dropShadow() để tạo hiệu ứng tân hình học:

@Composable
fun NeumorphicRaisedButton(
    shape: RoundedCornerShape = RoundedCornerShape(30.dp)
) {
    val bgColor = Color(0xFFe0e0e0)
    val lightShadow = Color(0xFFFFFFFF)
    val darkShadow = Color(0xFFb1b1b1)
    val upperOffset = -10.dp
    val lowerOffset = 10.dp
    val radius = 15.dp
    val spread = 0.dp
    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(bgColor)
            .wrapContentSize(Alignment.Center)
            .size(240.dp)
            .dropShadow(
                shape,
                shadow = Shadow(
                    radius = radius,
                    color = lightShadow,
                    spread = spread,
                    offset = DpOffset(upperOffset, upperOffset)
                ),
            )
            .dropShadow(
                shape,
                shadow = Shadow(
                    radius = radius,
                    color = darkShadow,
                    spread = spread,
                    offset = DpOffset(lowerOffset, lowerOffset)
                ),

            )
            .background(bgColor, shape)
    )
}

Một hình chữ nhật màu trắng có hiệu ứng tân hình thái học trên nền trắng.
Hình 6. Hiệu ứng đổ bóng theo phong cách tân hình học.

Tạo bóng đổ theo phong cách tân thô mộc

Phong cách tân thô mộc thể hiện bố cục dạng khối có độ tương phản cao, màu sắc sống động và đường viền dày. Để tạo hiệu ứng này, hãy dùng dropShadow() có độ mờ bằng 0 và độ lệch riêng biệt, như minh hoạ trong đoạn mã sau:

@Composable
fun NeoBrutalShadows() {
    SnippetsTheme {
        val dropShadowColor = Color(0xFF007AFF)
        val borderColor = Color(0xFFFF2D55)
        Box(Modifier.fillMaxSize()) {
            Box(
                Modifier
                    .width(300.dp)
                    .height(200.dp)
                    .align(Alignment.Center)
                    .dropShadow(
                        shape = RoundedCornerShape(0.dp),
                        shadow = Shadow(
                            radius = 0.dp,
                            spread = 0.dp,
                            color = dropShadowColor,
                            offset = DpOffset(x = 8.dp, 8.dp)
                        )
                    )
                    .border(
                        8.dp, borderColor
                    )
                    .background(
                        color = Color.White,
                        shape = RoundedCornerShape(0.dp)
                    )
            ) {
                Text(
                    "Neobrutal Shadows",
                    modifier = Modifier.align(Alignment.Center),
                    style = MaterialTheme.typography.bodyMedium
                )
            }
        }
    }
}

Một đường viền màu đỏ bao quanh một hình chữ nhật màu trắng có bóng đổ màu xanh dương trên nền màu vàng.
Hình 7. Hiệu ứng đổ bóng theo phong cách tân thô mộc.

Tạo bóng đổ chân thực

Bóng đổ chân thực mô phỏng bóng đổ trong thế giới thực – chúng xuất hiện dưới ánh sáng của một nguồn sáng chính, tạo ra cả bóng đổ trực tiếp và bóng đổ khuếch tán hơn. Bạn có thể xếp chồng nhiều thực thể dropShadow()innerShadow() với các thuộc tính khác nhau để tạo lại hiệu ứng đổ bóng chân thực, như minh hoạ trong đoạn mã sau:

@Composable
fun RealisticShadows() {
    Box(Modifier.fillMaxSize()) {
        val dropShadowColor1 = Color(0xB3000000)
        val dropShadowColor2 = Color(0x66000000)

        val innerShadowColor1 = Color(0xCC000000)
        val innerShadowColor2 = Color(0xFF050505)
        val innerShadowColor3 = Color(0x40FFFFFF)
        val innerShadowColor4 = Color(0x1A050505)
        Box(
            Modifier
                .width(300.dp)
                .height(200.dp)
                .align(Alignment.Center)
                .dropShadow(
                    shape = RoundedCornerShape(100.dp),
                    shadow = Shadow(
                        radius = 40.dp,
                        spread = 0.dp,
                        color = dropShadowColor1,
                        offset = DpOffset(x = 2.dp, 8.dp)
                    )
                )
                .dropShadow(
                    shape = RoundedCornerShape(100.dp),
                    shadow = Shadow(
                        radius = 4.dp,
                        spread = 0.dp,
                        color = dropShadowColor2,
                        offset = DpOffset(x = 0.dp, 4.dp)
                    )
                )
                // note that the background needs to be defined before defining the inner shadow
                .background(
                    color = Color.Black,
                    shape = RoundedCornerShape(100.dp)
                )
// //
                .innerShadow(
                    shape = RoundedCornerShape(100.dp),
                    shadow = Shadow(
                        radius = 12.dp,
                        spread = 3.dp,
                        color = innerShadowColor1,
                        offset = DpOffset(x = 6.dp, 6.dp)
                    )
                )
                .innerShadow(
                    shape = RoundedCornerShape(100.dp),
                    shadow = Shadow(
                        radius = 4.dp,
                        spread = 1.dp,
                        color = Color.White,
                        offset = DpOffset(x = 5.dp, 5.dp)
                    )
                )
                .innerShadow(
                    shape = RoundedCornerShape(100.dp),
                    shadow = Shadow(
                        radius = 12.dp,
                        spread = 5.dp,
                        color = innerShadowColor2,
                        offset = DpOffset(x = (-3).dp, (-12).dp)
                    )
                )
                .innerShadow(
                    shape = RoundedCornerShape(100.dp),
                    shadow = Shadow(
                        radius = 3.dp,
                        spread = 10.dp,
                        color = innerShadowColor3,
                        offset = DpOffset(x = 0.dp, 0.dp)
                    )
                )
                .innerShadow(
                    shape = RoundedCornerShape(100.dp),
                    shadow = Shadow(
                        radius = 3.dp,
                        spread = 9.dp,
                        color = innerShadowColor4,
                        offset = DpOffset(x = 1.dp, 1.dp)
                    )
                )

        ) {
            Text(
                "Realistic Shadows",
                modifier = Modifier.align(Alignment.Center),
                fontSize = 24.sp,
                color = Color.White
            )
        }
    }
}

Các điểm chính về mã

  • Hai đối tượng sửa đổi dropShadow() được liên kết với các thuộc tính riêng biệt sẽ được áp dụng, sau đó là đối tượng sửa đổi background().
  • Các đối tượng sửa đổi innerShadow() được liên kết sẽ được áp dụng để tạo hiệu ứng vành kim loại xung quanh cạnh của thành phần.

Kết quả

Đoạn mã trước đó tạo ra kết quả sau:

Một bóng đổ màu trắng chân thực xung quanh một hình tròn màu đen.
Hình 8. Hiệu ứng đổ bóng chân thực.