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 về tính tương tác và cung cấp phản hồi tức thì về các thao tác 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:

  • Modifier.shadow(): Tạo bóng đổ dựa trên độ cao 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 bóng đổ có thể tuỳ chỉnh xuất hiện phía sau một thành phần kết hợp, làm cho thành phần đó xuất hiện 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, làm cho thành phần đó xuất hiện như 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 và độ chính xác chi tiết hơn đối với việc hiển thị 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ách tạo ảnh động cho bóng đổ khi có lượt tương tác của người dùng và cách xâu chuỗi các đối tượng sửa đổi innerShadow()dropShadow() để tạo bóng đổ chuyển màu, bóng đổ kiểu neumorphic, và nhiều kiểu khác.

Tạo bóng đổ cơ bản

Modifier.shadow() tạo bóng đổ cơ bản tuân theo các nguyên tắc của Material Design, mô phỏng nguồn sáng từ phía trên. Độ sâu của bóng đổ dựa trên giá trị elevation và bóng đổ được chiếu sẽ bị 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 độ cao đượ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 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: Định vị hình học của bóng đổ dọc theo trục x và y.
  • spread: Kiểm soát việc mở rộng hoặc thu hẹp hình học của bóng đổ.

Ngoài ra, tham số shape xác định hình dạng tổng thể của bóng đổ. Tham số này 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 toả. Lưu ý rằng nền purpleColor xuất hiện ở trên cùng của bóng đổ được vẽ sau đối tượng 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 toả là 6.dp, giúp mở rộng kích thước của bóng đổ và làm cho bóng đổ lớn hơn hộp chiếu bóng
    • Alpha là 0.5f, giúp bóng đổ nửa trong suốt
  • Sau khi xác định bóng đổ, đối tượng sửa đổi .background() sẽ được áp dụng.
    • Box được tô màu trắng.
    • Nền được cắt theo cùng hình chữ nhật bo tròn 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. Bóng đổ được vẽ xung quanh hình dạng.

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

Để tạo hiệu ứng ngược lại với dropShadow(), hãy sử dụng Modifier.innerShadow(), tạo ảo giác rằng một phần tử bị lõm hoặc ép vào bề mặt bên dưới.

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

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

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

Ví dụ sau đây cho thấy cách á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 của Modifier.innerShadow() trên hình chữ nhật bo tròn góc.

Tạo ảnh động cho bóng đổ khi người dùng tương tác

Để làm cho bóng đổ phản hồi các lượt 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 ảnh động của Compose. Ví dụ: khi người dùng nhấn một nút, bóng đổ có thể thay đổi để cung cấp phản hồi trực quan tức thì.

Mã sau đây tạo hiệu ứng "đã nhấn" bằng 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à kết thúc cho các tham số để 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ả ảnh độ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 sang giá trị mục tiêu mới.
  • Xác định thông số kỹ thuật buttonPressAnimation, kiểm soát thời gian và độ giảm nhẹ của quá trình chuyển đổi. Thông số này chỉ định một tween (viết tắt của in-between) có thời lượng 400 mili giây và đường cong EaseInOut, nghĩa là ảnh động bắt đầu chậm, tăng tốc ở giữa và chậm lại ở cuối.
  • Xác định Box bằng một chuỗi các hàm sửa đổi áp dụng tất cả các thuộc tính được tạo ảnh động để tạo phần tử trực quan, bao gồm:
    • .clickable(): Đố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. Thuộc tính màu và alpha của chúng được liên kết với các giá trị được tạo ảnh động (blueDropShadow, v.v.) và tạo ra hình dạng ban đầu được nâng lên.
    • .innerShadow(): Hai bóng đổ bên trong được vẽ ở trên cùng của nền. Thuộc tính của chúng được liên kết với tập hợp giá trị được tạo ảnh động khác (innerShadowColor1, v.v.) và tạo ra hình dạng thụt vào.

Kết quả

Hình 4. Bóng đổ tạo ảnh độ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 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ô màu bóng đổ bằng hiệu ứng chuyển màu xoay qua danh sách colors được xác định trước, tạo hiệu ứng giống như cầu vồng.

Kết quả

Bạn có thể sử dụng bút vẽ làm bóng đổ để tạo dropShadow() chuyển màu với ảnh động "thở":

Hình 5. Bóng đổ chuyển màu được tạo ảnh động.

Kết hợp bóng đổ

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

Tạo bóng đổ kiểu neumorphic

Bóng đổ kiểu neumorphic có đặc điểm là hình dạng mềm mại xuất hiện một cách tự nhiên từ nền. Để tạo bóng đổ kiểu neumorphic, 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 2 bóng đổ mờ đối diện: một bóng đổ sáng ở một góc và một bóng đổ tối ở 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 neumorphic:

@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 mô phỏng trên nền trắng.
Hình 6. Hiệu ứng bóng đổ kiểu neumorphic.

Tạo bóng đổ kiểu neobrutalist

Phong cách neobrutalist 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 sử dụng dropShadow() có độ mờ bằng 0 và độ lệch rõ rệ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
                )
            }
        }
    }
}

Đườ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 đổ kiểu neobrutalist.

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 như được chiếu sáng bởi 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ái tạo 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 xâu chuỗi với các thuộc tính riêng biệt được áp dụng, tiếp theo là đối tượng sửa đổi background().
  • Các đối tượng sửa đổi innerShadow() được xâu chuỗi đượ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.