Modifiers trong Jetpack Compose
Modifiers cho phép bạn trang trí hoặc thay đổi hành vi của một composable. Đây là một trong những khái niệm quan trọng nhất trong Compose mà bạn sẽ sử dụng hàng ngày.
1. Modifier là gì?
Modifier là một chuỗi các “decorators” được áp dụng lên composable để:
- Thay đổi kích thước và vị trí
- Thêm background, border, shadow
- Xử lý sự kiện (click, scroll, drag)
- Thay đổi accessibility properties
@Composable
fun GreetingCard() {
Text(
text = "Hello Compose",
modifier = Modifier
.fillMaxWidth() // Chiếm toàn bộ chiều rộng
.padding(16.dp) // Thêm padding
.background(Color.LightGray) // Nền xám
.clickable { } // Có thể click
)
}2. Thứ tự Modifiers RẤT QUAN TRỌNG
Modifiers được áp dụng từ trái sang phải (hoặc từ trên xuống dưới). Thứ tự khác nhau → kết quả khác nhau!
Ví dụ: padding trước vs sau background
// padding TRƯỚC background
Modifier
.padding(16.dp) // 1. Padding bên ngoài
.background(Color.Blue) // 2. Background sau padding
// → Background chỉ bao phủ phần trong
// padding SAU background
Modifier
.background(Color.Blue) // 1. Background trước
.padding(16.dp) // 2. Padding bên trong
// → Background bao phủ cả paddingVí dụ trực quan
@Composable
fun ModifierOrderDemo() {
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
// Padding trước background
Box(
modifier = Modifier
.padding(8.dp)
.background(Color.Blue)
.size(50.dp)
)
// Padding sau background
Box(
modifier = Modifier
.background(Color.Red)
.padding(8.dp)
.size(50.dp)
)
}
}Quy tắc nhớ
- Padding ngoài → dùng
padding()TRƯỚCbackground() - Padding trong → dùng
padding()SAUbackground() - Clickable area →
clickable()SAUpadding()để click cả padding
3. Các Modifiers phổ biến
3.1. Size Modifiers
Modifier
.size(100.dp) // Width = Height = 100dp
.size(width = 150.dp, height = 100.dp) // Width ≠ Height
.width(200.dp) // Chỉ set width
.height(100.dp) // Chỉ set height
.fillMaxWidth() // 100% parent width
.fillMaxHeight() // 100% parent height
.fillMaxSize() // 100% cả width và height
.fillMaxWidth(0.5f) // 50% parent width
.wrapContentSize() // Fit content
.requiredSize(100.dp) // Bắt buộc size, ignore constraints3.2. Padding và Spacing
Modifier
.padding(16.dp) // Tất cả các cạnh
.padding(horizontal = 16.dp, vertical = 8.dp)
.padding(start = 8.dp, end = 16.dp, top = 4.dp, bottom = 12.dp)3.3. Background và Border
Modifier
.background(Color.Blue) // Solid color
.background( // Gradient
brush = Brush.horizontalGradient(
colors = listOf(Color.Blue, Color.Purple)
)
)
.background(Color.Blue, RoundedCornerShape(8.dp)) // Với corner
.border(2.dp, Color.Black) // Border
.border(2.dp, Color.Black, CircleShape) // Border với shape3.4. Shape và Clip
Modifier
.clip(RoundedCornerShape(16.dp)) // Bo góc
.clip(CircleShape) // Hình tròn
.clip(CutCornerShape(8.dp)) // Cắt góc
.shadow(8.dp, RoundedCornerShape(16.dp)) // Shadow3.5. Click và Touch
Modifier
.clickable { /* handle click */ }
.clickable(
enabled = true,
onClick = { }
)
.combinedClickable(
onClick = { },
onLongClick = { },
onDoubleClick = { }
)
.selectable(selected = isSelected, onClick = { })
.toggleable(value = isOn, onValueChange = { })3.6. Scroll
Modifier
.verticalScroll(rememberScrollState())
.horizontalScroll(rememberScrollState())
.scrollable(
state = rememberScrollableState { delta -> delta },
orientation = Orientation.Vertical
)3.7. Layout và Alignment
Modifier
.offset(x = 10.dp, y = 20.dp) // Di chuyển vị trí
.offset { IntOffset(10.dp.roundToPx(), 0) } // Dynamic offset
.align(Alignment.Center) // Trong Box
.align(Alignment.CenterVertically) // Trong Row
.align(Alignment.CenterHorizontally) // Trong Column
.weight(1f) // Trong Row/Column3.8. Drawing
Modifier
.alpha(0.5f) // Trong suốt
.rotate(45f) // Xoay 45 độ
.scale(1.5f) // Scale up 150%
.graphicsLayer { // Advanced transformations
rotationZ = 45f
scaleX = 1.5f
translationX = 10f
}4. Scope Safety - Modifiers chỉ dùng trong context
Một số Modifiers chỉ available trong scope nhất định:
4.1. matchParentSize trong Box
Box(modifier = Modifier.size(200.dp)) {
// ✅ Chỉ dùng được trong BoxScope
Box(
modifier = Modifier
.matchParentSize() // Khớp với parent, không ảnh hưởng parent size
.background(Color.Blue)
)
// Nếu dùng fillMaxSize(), nó sẽ yêu cầu parent lớn nhất có thể
}4.2. weight trong Row/Column
Row(modifier = Modifier.fillMaxWidth()) {
// ✅ Chỉ dùng được trong RowScope
Text("Left", modifier = Modifier.weight(1f))
Text("Right", modifier = Modifier.weight(2f))
}
Column(modifier = Modifier.fillMaxHeight()) {
// ✅ Chỉ dùng được trong ColumnScope
Text("Top", modifier = Modifier.weight(1f))
Text("Bottom", modifier = Modifier.weight(1f))
}4.3. align trong Box/Row/Column
Box(modifier = Modifier.fillMaxSize()) {
// BoxScope.align()
Text("Center", modifier = Modifier.align(Alignment.Center))
}
Row(modifier = Modifier.height(100.dp)) {
// RowScope.align() - chỉ vertical
Text("Bottom", modifier = Modifier.align(Alignment.Bottom))
}
Column(modifier = Modifier.width(200.dp)) {
// ColumnScope.align() - chỉ horizontal
Text("End", modifier = Modifier.align(Alignment.End))
}5. Tái sử dụng Modifiers
5.1. Extract ra biến
// Tạo modifier chung
val cardModifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.clip(RoundedCornerShape(8.dp))
.background(Color.White)
.shadow(4.dp)
@Composable
fun ProductCard(product: Product) {
Card(modifier = cardModifier) {
// ...
}
}5.2. Cho phép customize từ bên ngoài
@Composable
fun PrimaryButton(
text: String,
onClick: () -> Unit,
modifier: Modifier = Modifier // Luôn có parameter này
) {
Button(
onClick = onClick,
modifier = modifier // Apply modifier từ caller trước
.height(48.dp)
) {
Text(text)
}
}
// Sử dụng
PrimaryButton(
text = "Submit",
onClick = { },
modifier = Modifier.fillMaxWidth() // Caller customize
)5.3. Best Practices
// ✅ ĐÚNG: Modifier parameter ở đầu (sau required params)
@Composable
fun MyComponent(
title: String, // Required
modifier: Modifier = Modifier, // Modifier
subtitle: String = "" // Optional
) { }
// ✅ ĐÚNG: Chain modifier từ caller với internal modifiers
@Composable
fun CustomCard(
modifier: Modifier = Modifier
) {
Card(
modifier = modifier
.fillMaxWidth() // Internal modifiers sau caller's
.padding(16.dp)
) { }
}6. Tạo Custom Modifier
6.1. Extension function đơn giản
// Custom modifier để làm round corners với background
fun Modifier.roundedBackground(
color: Color,
cornerRadius: Dp = 8.dp
): Modifier = this
.clip(RoundedCornerShape(cornerRadius))
.background(color)
// Sử dụng
Text(
"Custom",
modifier = Modifier.roundedBackground(Color.Blue)
)6.2. Modifier với composed
Khi cần tạo state hoặc side effects trong modifier:
fun Modifier.shake(enabled: Boolean): Modifier = composed {
val offset = remember { Animatable(0f) }
LaunchedEffect(enabled) {
if (enabled) {
offset.animateTo(
targetValue = 10f,
animationSpec = spring(stiffness = Spring.StiffnessHigh)
)
offset.animateTo(0f)
}
}
this.offset { IntOffset(offset.value.roundToInt(), 0) }
}📝 Tóm tắt
| Category | Modifiers |
|---|---|
| Size | size, width, height, fillMax*, wrapContent |
| Spacing | padding, offset |
| Appearance | background, border, clip, shadow, alpha |
| Drawing | rotate, scale, graphicsLayer |
| Interaction | clickable, scrollable, draggable |
| Layout | align, weight, matchParentSize |
Checklist khi dùng Modifiers
- Thứ tự modifiers có đúng không? (padding vs background)
- Có truyền modifier parameter cho custom composables không?
- Modifier từ caller được áp dụng trước internal modifiers?
- Scope-specific modifiers chỉ dùng trong đúng scope?
Last updated on