Shadow (bóng đổ) — hiệu ứng trực quan trong giao diện di động tạo ra ảo giác về chiều sâu và hệ thống phân cấp giữa các phần tử. Bóng đổ cho thấy phần tử nào ở cao hơn trong mặt phẳng z và do đó, quan trọng hơn hoặc có tính tương tác cao hơn. Theo Material Design Guidelines (Google, 2026), bóng đổ hệ thống sử dụng năm độ cao (elevation) chính: 0 dp (phẳng), 1–4 dp (nút), 6–12 dp (thẻ), 16–24 dp (hộp thoại), 24+ dp (cửa sổ phương thức). Tìm hiểu thêm về chiều cao phần tử trong bài viết về Elevation.
Những điểm chính
Bóng đổ (shadow) trong giao diện người dùng là hiệu ứng trực quan mô phỏng ánh sáng và chiều sâu. Bóng đổ được tạo bằng cách vẽ một bản sao tối và mờ của phần tử, dịch chuyển so với bản gốc. Trong thiết kế di động, bóng đổ thực hiện ba chức năng: hiển thị hệ thống phân cấp (phần tử càng cao, bóng càng dài), báo hiệu tính tương tác (nút có bóng trông có thể nhấn được), tách nội dung khỏi nền (thẻ có bóng được đọc như các khối riêng biệt).
Trong Material Design của Google, bóng đổ gắn liền với khái niệm elevation. Mỗi phần tử chiếm một độ cao nhất định trong tọa độ z và bóng đổ là hình chiếu của ánh sáng từ trên xuống. Human Interface Guidelines của Apple không quy định hệ thống độ cao nghiêm ngặt, nhưng khuyến nghị sử dụng bóng đổ để tạo hệ thống phân cấp trực quan và chiều sâu trong giao diện.
Theo một nghiên cứu của Nielsen Norman Group (2024), bóng đổ được đặt đúng cách giúp giảm thời gian tìm phần tử tương tác xuống 24%. Bóng đổ cũng giúp người dùng khiếm thị phân biệt ranh giới của các phần tử. Tuy nhiên, việc sử dụng quá nhiều bóng đổ (3+ cấp độ trên một màn hình) làm tăng tải nhận thức và làm giảm khả năng nhận biết.
Trên iOS, bóng đổ được triển khai thông qua các thuộc tính CALayer: shadowColor đặt màu bóng, shadowOffset — độ dịch chuyển trong CGSize (x, y), shadowRadius — bán kính làm mờ, shadowOpacity — độ mờ đục từ 0 đến 1. Để có hiệu suất tốt, cũng nên đặt shadowPath — một UIBezierPath mô tả hình dạng bóng, để Core Animation không tự động tính toán.
// UIKit: cấu hình bóng đổ qua CALayer
import UIKit
let cardView = UIView()
cardView.backgroundColor = .systemBackground
cardView.layer.cornerRadius = 12
// Cấu hình bóng đổ
cardView.layer.shadowColor = UIColor.black.cgColor
cardView.layer.shadowOpacity = 0.15
cardView.layer.shadowOffset = CGSize(width: 0, height: 4)
cardView.layer.shadowRadius = 8
"> Tối ưu hóa: shadowPath rõ ràng
cardView.layer.shadowPath = UIBezierPath(
roundedRect: cardView.bounds,
cornerRadius: 12
).cgPath
Trong SwiftUI, bóng đổ được đặt thông qua bộ sửa đổi .shadow() với các tham số color, radius, x, y. SwiftUI tự động áp dụng shadowPath và quản lý hiệu suất kết xuất. Đối với bóng đổ nâng cao (bóng bên trong, nhiều bóng), hãy sử dụng overlay() với LinearGradient.
// SwiftUI: bóng đổ qua bộ sửa đổi shadow
import SwiftUI
VStack {
Text("Thẻ có bóng đổ")
.padding()
.background(Color.white)
.cornerRadius(16)
.shadow(
color: .black.opacity(0.15),
radius: 10,
x: 0,
y: 4
)
}
Hiệu suất bóng đổ trên iOS: nếu không đặt shadowPath, Core Animation sẽ tính toán bóng đổ dựa trên hình dạng của lớp mỗi khung hình, có thể gây giật lag trong quá trình hoạt ảnh. Đối với phần tử tĩnh, luôn đặt shadowPath. Đối với phần tử hoạt ảnh, hãy sử dụng shouldRasterize = true trên CALayer để lưu bóng đổ vào bộ nhớ đệm trong một lớp riêng. Trong SwiftUI, việc rasterization được quản lý thông qua .drawingGroup().
Trên Android, bóng đổ trong Material Design được triển khai thông qua elevation — thuộc tính View đặt chiều cao của phần tử trong tọa độ z. Hệ thống tự động kết xuất bóng đổ dựa trên elevation và outlineProvider (đường viền phần tử). elevation càng cao, bóng đổ càng lớn và mờ. Đối với View không kế thừa Material Design (ví dụ: AppCompat), bóng đổ chỉ hoạt động khi tăng tốc phần cứng được bật.
<!-- XML: elevation qua thuộc tính -->
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="12dp"
app:cardElevation="6dp">
<!-- Nội dung thẻ -->
</androidx.cardview.widget.CardView>
Đối với bóng đổ tùy chỉnh trong Hệ thống View Android, hãy sử dụng OutlineProvider. ViewOutlineProvider cho phép đặt hình dạng tùy ý thông qua outline.setRoundRect() hoặc outline.setPath(). Nếu không có OutlineProvider, bóng đổ được kết xuất dựa trên hình dạng hình chữ nhật của View, không tính đến độ bo tròn góc.
// Kotlin: OutlineProvider tùy chỉnh cho bóng đổ
import android.graphics.Outline
import android.view.View
import android.view.ViewOutlineProvider
val myView: View = findViewById(R.id.myView)
myView.outlineProvider = object : ViewOutlineProvider() {
override fun getOutline(view: View, outline: Outline) {
outline.setRoundRect(
0, 0, view.width, view.height,
12f // bán kính góc
)
}
}
myView.elevation = 8f // chiều cao bóng đổ tính bằng px
myView.translationZ = 4f // chiều cao bổ sung khi nhấn
Trong Jetpack Compose, bóng đổ được quản lý thông qua Modifier.shadow() và elevation trong các thành phần Card và Surface. Modifier.shadow() chấp nhận elevation và shape. Không giống như Hệ thống View, Compose kết xuất bóng đổ trong đường dẫn kết xuất riêng của nó, cung cấp hành vi dễ dự đoán hơn trên tất cả các phiên bản Android.
// Jetpack Compose: bóng đổ qua Modifier.shadow
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
Card(
modifier = Modifier.shadow(
elevation = 6.dp,
shape = RoundedCornerShape(12.dp),
clip = false
),
colors = CardDefaults.cardColors(containerColor = Color.White)
) {
Text("Card với bóng đổ tùy chỉnh")
}
Sử dụng bóng đổ đúng cách cải thiện nhận thức về giao diện, trong khi sử dụng sai cách tạo ra nhiễu thị giác và làm giảm hiệu suất. Material Design định nghĩa năm cấp độ elevation: 0 dp (không bóng), 1–4 dp (nút, chip), 6–12 dp (thẻ, menu), 16–24 dp (hộp thoại, trang dưới), 24+ dp (cửa sổ phương thức, FAB).
| Cấp độ | Elevation | Phần tử | Đặc điểm bóng đổ |
|---|---|---|---|
| 0 | 0 dp | Văn bản, biểu tượng, nền | Không bóng, lớp phẳng |
| 1 | 1–4 dp | Nút, trường nhập | Bóng nhẹ, dịch chuyển nhỏ |
| 2 | 6–12 dp | Thẻ, Snackbar | Bóng vừa phải, làm mờ rõ rệt |
| 3 | 16–24 dp | Hộp thoại, menu | Bóng sâu, làm mờ lớn |
| 4 | 24+ dp | Cửa sổ phương thức | Bóng tối đa, phần tử lơ lửng |
Thực hành tốt nhất: (1) không sử dụng bóng đổ cho các phần tử không nên thu hút sự chú ý (nền, dải phân cách), (2) chiều cao bóng đổ phải tương ứng với tầm quan trọng của phần tử — càng quan trọng càng cao, (3) khi nhấn nút, giảm elevation kèm hoạt ảnh để phản hồi xúc giác, (4) tránh bóng đổ trên nền tối — sử dụng overlay (làm sáng/tối) thay vì bóng. Trong Material You (Android 12+), bóng đổ thích ứng với chủ đề: trên nền tối, elevation được thay thế bằng một lớp màu (surface tint).
Trong Flutter, bóng đổ được triển khai thông qua BoxShadow bên trong BoxDecoration. BoxShadow chấp nhận color, offset, blurRadius và spreadRadius. Flutter hỗ trợ nhiều bóng đổ: truyền một danh sách BoxShadow cho phép tạo các hiệu ứng phức tạp — ví dụ: bóng bên ngoài + điểm nhấn bên trong. PhysicalModel là một giải pháp thay thế mô phỏng bóng đổ vật lý dựa trên hình dạng (BeveledRectangleBorder, CircleBorder).
// Flutter: bóng đổ qua BoxDecoration + BoxShadow
import 'package:flutter/material.dart';
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.0),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.15),
offset: Offset(0, 4),
blurRadius: 8.0,
spreadRadius: 0,
),
],
),
child: Text('Vùng chứa có bóng đổ'),
)
// PhysicalModel cho bóng vật lý
PhysicalModel(
color: Colors.white,
elevation: 6.0,
shadowColor: Colors.black.withOpacity(0.2),
borderRadius: BorderRadius.circular(12.0),
child: Text('Mô hình vật lý có bóng đổ'),
)
Hiệu suất: BoxShadow với nhiều bóng đổ có thể gây vẽ lại mỗi khi thay đổi. Đối với phần tử tĩnh, hãy sử dụng PhysicalModel — nó lưu bóng đổ vào bộ nhớ đệm trong một lớp riêng (RepaintBoundary). Trong Flutter 3.16+, elevation Material 3 với bóng đổ thích ứng được hỗ trợ, thay đổi màu sắc và độ mờ tùy theo chủ đề (sáng/tối).
Câu hỏi thường gặp
Elevation là chiều cao số học của phần tử trong tọa độ z, trong khi shadow (bóng đổ) là hiệu ứng trực quan được tạo ra bởi chiều cao này. Trong Android, elevation là thuộc tính View tự động tạo bóng đổ. Trong thiết kế, elevation và shadow thường được sử dụng thay thế cho nhau, nhưng về mặt kỹ thuật, elevation là nguyên nhân và shadow là kết quả.
Kiểm tra: (1) View có elevation lớn hơn 0, (2) outlineProvider đã được đặt (nếu View không phải từ thư viện Material), (3) tăng tốc phần cứng được bật (android:hardwareAccelerated="true" trong tệp kê khai). CardView tự động quản lý outlineProvider. Đối với View tùy chỉnh, hãy gọi view.outlineProvider = ViewOutlineProvider.BACKGROUND.
CALayer không hỗ trợ bóng bên trong trực tiếp. Sử dụng kết hợp: CAGradientLayer với màu đen và độ trong suốt bên trong mặt nạ, hoặc phủ một UIImageView với bóng bên trong raster. Trong SwiftUI, bóng bên trong được triển khai thông qua overlay với LinearGradient và blendMode(.multiply).
Trong Android, sử dụng translationZ cho hoạt ảnh: ở trạng thái nghỉ elevation = 2 dp, khi nhấn translationZ = 4 dp (tổng chiều cao 6 dp). Trên iOS, tạo hoạt ảnh shadowOffset và shadowRadius đồng thời với biến đổi nút. Trong SwiftUI, sử dụng withAnimation với thay đổi elevation qua Modifier.shadow(). Trong Flutter, sử dụng AnimatedContainer với thay đổi BoxShadow.
Material Design khuyến nghị không quá ba cấp độ elevation trên một màn hình. Nếu có nhiều phần tử có bóng đổ hơn, hãy nhóm chúng với cùng độ cao. Số lượng bóng đổ quá nhiều tạo ra nhiễu thị giác và làm giảm hiệu suất kết xuất trên các thiết bị tầm trung và bình dân.
Tóm tắt
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm