Modifier là một đối tượng bất biến trong Jetpack Compose xác định các thuộc tính của thành phần UI: kích thước, đệm, nền, xử lý cử chỉ và hành vi. Các bộ sửa đổi được kết hợp thành chuỗi thông qua các lệnh gọi tuần tự, và thứ tự áp dụng của chúng ảnh hưởng nghiêm trọng đến kết quả. Theo Google Android Developers, 2026, việc sử dụng Modifier đúng cách là nền tảng để xây dựng giao diện linh hoạt và hiệu suất cao trong UI khai báo.
Những điểm chính
Modifier là một giao diện từ gói androidx.compose.ui thực hiện mẫu Composite. Mỗi bộ sửa đổi là một phần tử chuỗi bao bọc phần tử trước và thêm hành vi riêng của nó. Modifier là bất biến — mọi thay đổi tạo ra đối tượng mới bằng cách sao chép với một phần tử mới được thêm vào chuỗi. Điều này cho phép chia sẻ an toàn một Modifier duy nhất giữa nhiều thành phần.
Các hàm sửa đổi cơ bản được gọi thông qua đối tượng bạn đồng hành Modifier (ví dụ: Modifier.padding(), Modifier.fillMaxWidth()). Mỗi hàm trả về một Modifier mới với phần tử được thêm vào. Nếu có nhiều bộ sửa đổi, chúng được kết hợp thành một chuỗi: Modifier.padding(16.dp).fillMaxWidth().background(Color.Blue). Thứ tự đi từ ngoài vào trong so với phần tử UI.
Không giống View truyền thống nơi các thuộc tính được đặt qua setter (view.setPadding(...), view.setBackground(...)), trong Compose Modifier là một mô tả khai báo. Thành phần không áp dụng các bộ sửa đổi trong thời gian chạy — LayoutNode duyệt qua chuỗi Modifier trong quá trình hợp thành và xây dựng danh sách Modifier.Element, sau đó được xử lý trong các giai đoạn đo lường và bố trí.
Thứ tự của các bộ sửa đổi là một trong những lỗi phổ biến nhất trong Compose. Mỗi bộ sửa đổi bao bọc phần tử trước và các thao tác được áp dụng từ ngoài vào trong. Ví dụ, padding(16.dp).clickable { }: đầu tiên đệm được thêm vào xung quanh phần tử, sau đó vùng nhấp bao gồm cả đệm. clickable { }.padding(16.dp): vùng nhấp bằng kích thước phần tử trước, sau đó đệm được thêm vào xung quanh — nhấp vào đệm sẽ không hoạt động.
Quy tắc ghi nhớ: đọc chuỗi từ trái sang phải và áp dụng từ ngoài vào trong. Bộ sửa đổi đầu tiên là ngoài cùng, áp dụng cho vùng xung quanh phần tử. Bộ cuối cùng là trong cùng, áp dụng trực tiếp lên nội dung. Các bộ sửa đổi kích thước (size, fillMaxWidth) nên đứng sau đệm nếu cần đệm từ phần tử cha, hoặc trước đệm nếu nội dung cần được giới hạn trước rồi căn giữa.
Ví dụ: size(100.dp).padding(10.dp) — phần tử kích thước cố định 100dp, sau đó đệm 10dp bên ngoài (kích thước cuối cùng 120dp). padding(10.dp).size(100.dp) — đệm 10dp làm giảm không gian khả dụng xuống (cha - 20dp), sau đó size(100dp) có thể tràn ra ngoài phần tử cha. Luôn suy nghĩ thấu đáo về thứ tự, sử dụng các bài kiểm tra hiển thị để xác nhận kết quả.
| Thứ tự | Kết quả |
|---|---|
| padding → clickable | Nhấp hoạt động trên cả vùng đệm |
| clickable → padding | Nhấp chỉ hoạt động trên nội dung, đệm là vùng chết |
| size → padding | Phần tử size(100), đệm bên ngoài → 100+2*pad |
| padding → size | Đệm làm giảm không gian, size có thể vượt quá giới hạn |
| background → padding | Nền lấp đầy toàn bộ phần tử bao gồm cả vùng bên ngoài |
| padding → background | Nền chỉ bên trong đệm (vùng bên ngoài trong suốt) |
Thư viện Compose tiêu chuẩn bao gồm ~50+ bộ sửa đổi được chia thành các danh mục. Kích thước và định vị: Modifier.size(), width(), height(), fillMaxSize(), fillMaxWidth(), fillMaxHeight(), defaultMinSize(), requiredSize(). Đệm và đường viền: padding(), offset(), margin (được đặt qua đệm cha hoặc Layout). Trang trí: background(), border(), clip(), alpha(), shadow(), blur().
Hành vi và cử chỉ: clickable(), combinedClickable(), pointerInput(), draggable(), swipeable(). Bố trí trong container: weight() (cho Row/Column), align(), alignBy(), matchParentSize(). Ngữ nghĩa và khả năng tiếp cận: semantics(), testTag(), clearAndSetSemantics(). Vẽ: drawBehind(), drawWithContent(), drawModifier() — các bộ sửa đổi cho phép vẽ tùy chỉnh trên canvas.
Bộ sửa đổi ngữ nghĩa là một danh mục đặc biệt. Modifier.semantics {} xác định cách phần tử sẽ được trình bày trong cây khả năng tiếp cận. Compose tự động điền ngữ nghĩa từ văn bản, nhưng các thành phần tùy chỉnh cần đặt vai trò, trạng thái và hành động thủ công. Điều này rất quan trọng để tuân thủ WCAG 2.2 và hoạt động chính xác của TalkBack (Android) và VoiceOver (iOS).
@Composable
fun ModifierDemo() {
// Chuỗi sửa đổi với thứ tự đúng
Box(
modifier = Modifier
.size(150.dp)
.padding(8.dp)
.border(2.dp, Color.Gray)
.background(Color(0xFFE3F2FD))
.clickable { /* handle click */ }
.semantics {
contentDescription = "Demo card with click action"
role = Role.Button
}
) {
Text("Chạm vào tôi")
}
}
Modifier.composed là một phương thức factory cho phép tạo các bộ sửa đổi tổng hợp có thể sử dụng các bộ sửa đổi khác, LocalComposition và trạng thái cục bộ. Không giống hàm mở rộng thông thường, composed tạo một thể hiện mỗi khi được áp dụng, cho phép bộ sửa đổi có trạng thái riêng.
Khi nào sử dụng composed: các kết hợp lặp lại của các bộ sửa đổi (ví dụ: kiểu thẻ tiêu chuẩn: padding + background + border + clickable); bộ sửa đổi có trạng thái (thay đổi nền có hoạt ảnh khi nhấn); truy cập CompositionLocals (bảng màu MaterialTheme, mật độ pixel). Đối với các trường hợp thông thường, một hàm mở rộng thông thường không có composed là đủ.
Hiệu suất của composed: mỗi lệnh gọi tạo một đối tượng sửa đổi mới, có thể dẫn đến cấp phát thêm trong quá trình tái hợp. Để ngăn chặn điều này, hãy bọc composed trong remember. Google khuyến nghị chỉ sử dụng composed khi thực sự cần trạng thái hoặc CompositionLocal bên trong. Đối với các kết hợp tĩnh, hãy sử dụng các hàm mở rộng thông thường.
// Sửa đổi tùy chỉnh qua composed với trạng thái
fun Modifier.cardStyle(
elevation: Dp = 4.dp,
isSelected: Boolean = false
): Modifier = this.composed {
val backgroundColor = if (isSelected)
MaterialTheme.colorScheme.primaryContainer
else
MaterialTheme.colorScheme.surface
this
.fillMaxWidth()
.padding(12.dp)
.background(backgroundColor, RoundedCornerShape(8.dp))
.shadow(elevation, RoundedCornerShape(8.dp))
}
// Ví dụ sử dụng
@Composable
fun CardList() {
Column {
Box(Modifier.cardStyle()) { Text("Mục 1") }
Box(Modifier.cardStyle(isSelected = true)) { Text("Đã chọn") }
}
}
// Phiên bản tĩnh (không composed) — nhanh hơn
fun Modifier.simpleCardStyle(): Modifier =
this.fillMaxWidth().padding(8.dp).clip(RoundedCornerShape(4.dp))
Tránh tạo lại Modifier ở mỗi lần tái hợp. Nếu bộ sửa đổi không phụ thuộc vào dữ liệu có thể thay đổi — hãy chuyển nó thành hằng số hoặc remember. Mỗi lần gọi Modifier.padding().background() tạo các đối tượng Modifier.Element mới. Trong một thành phần riêng lẻ, điều này không đáng kể, nhưng trong LazyColumn với hàng trăm phần tử, việc cấp phát thêm gây ra độ trễ đáng kể khi cuộn.
Quy tắc: nếu chuỗi sửa đổi không phụ thuộc vào tham số của hàm Composable — hãy khai báo nó là val bên ngoài hàm (ở cấp tập tin hoặc Companion). Nếu phụ thuộc — hãy sử dụng remember(phụ thuộc) { ... }. Đối với các bộ sửa đổi luôn giống nhau, val bên ngoài Composable là hiệu quả nhất: các đối tượng này được tạo một lần trong suốt vòng đời ứng dụng.
Các phương pháp tốt nhất về thứ tự Modifier: đặt các bộ sửa đổi theo thứ tự logic: đầu tiên kích thước/đệm (bố trí), sau đó trang trí (background, border), sau đó hành vi (clickable, pointerInput). Điều này không chỉ cải thiện khả năng đọc mà còn giúp Compose Runtime tối ưu hóa chuỗi trong quá trình đo lường. Cũng tránh các phần tử Box lồng nhau quá mức với các Modifier khác nhau — thường một Modifier duy nhất trên container cha có thể thay thế 2-3 phần tử lồng nhau.
// ✅ Tốt: hằng số bên ngoài Composable
private val cardModifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.clip(RoundedCornerShape(8.dp))
@Composable
fun CardContent() {
Box(cardModifier.background(Color.White)) { ... }
}
// ❌ Xấu: tạo lại ở mỗi lần tái hợp
@Composable
fun BadCard() {
Box(Modifier.fillMaxWidth().padding(16.dp)) { ... }
}
// ✅ Tốt: remember cho Modifier động
@Composable
fun DynamicCard(color: Color) {
val modifier = remember(color) {
Modifier.fillMaxWidth().background(color)
}
Box(modifier) { ... }
}
Câu hỏi thường gặp
Có, Modifier là bất biến, vì vậy một đối tượng có thể được sử dụng an toàn ở nhiều nơi. Tuy nhiên, nếu bạn sử dụng bộ sửa đổi composed, mỗi lần gọi tạo một thể hiện mới. Đối với các chuỗi tĩnh, một hằng số hoặc val bên ngoài Composable là giải pháp tối ưu.
Sử dụng Layout Inspector trong Android Studio — nó hiển thị trực quan ranh giới của mỗi Modifier. Để gỡ lỗi lập trình, thêm Modifier.border() với các màu khác nhau ở mỗi bước của chuỗi để thấy ranh giới của từng bộ sửa đổi.
Modifier.then(other) gắn chuỗi other vào this. Các lệnh gọi tuần tự (Modifier.a().b()) tương đương với Modifier.then(a()).then(b()). Không có sự khác biệt — đó là cùng một cơ chế chuỗi. then() hữu ích khi bạn cần gắn một chuỗi có sẵn từ một biến.
Modifier.semantics {} xác định cách phần tử sẽ được mô tả cho trình đọc màn hình. Modifier.clickable() tự động thêm vai trò Nút và Action(OnClick). Đối với cử chỉ tùy chỉnh, bạn cần chỉ định rõ ràng semantics. Nếu không có bộ sửa đổi ngữ nghĩa, người dùng TalkBack sẽ không thể tương tác với các thành phần tùy chỉnh.
Modifier.background(color, shape) hoạt động với các góc, nhưng clip() phải đằng TRƯỚC background để các góc được cắt. Thứ tự đúng: clip(shape).background(color). Nếu bạn cũng cần cắt nội dung bên trong, hãy sử dụng clipToBounds() trên phần tử cha.
Tổng kế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