Modifiers chi tiết
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ì?
Phần tiêu đề “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
@Composablefun 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
Phần tiêu đề “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
Phần tiêu đề “Ví dụ: padding trước vs sau background”// padding TRƯỚC backgroundModifier .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 backgroundModifier .background(Color.Blue) // 1. Background trước .padding(16.dp) // 2. Padding bên trong// → Background bao phủ cả paddingVí dụ trực quan
Phần tiêu đề “Ví dụ trực quan”@Composablefun 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ớ
Phần tiêu đề “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
Phần tiêu đề “3. Các Modifiers phổ biến”3.1. Size Modifiers
Phần tiêu đề “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
Phần tiêu đề “3.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
Phần tiêu đề “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
Phần tiêu đề “3.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
Phần tiêu đề “3.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
Phần tiêu đề “3.6. Scroll”Modifier .verticalScroll(rememberScrollState()) .horizontalScroll(rememberScrollState()) .scrollable( state = rememberScrollableState { delta -> delta }, orientation = Orientation.Vertical )3.7. Layout và Alignment
Phần tiêu đề “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
Phần tiêu đề “3.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
Phần tiêu đề “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
Phần tiêu đề “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
Phần tiêu đề “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
Phần tiêu đề “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
Phần tiêu đề “5. Tái sử dụng Modifiers”5.1. Extract ra biến
Phần tiêu đề “5.1. Extract ra biến”// Tạo modifier chungval cardModifier = Modifier .fillMaxWidth() .padding(16.dp) .clip(RoundedCornerShape(8.dp)) .background(Color.White) .shadow(4.dp)
@Composablefun ProductCard(product: Product) { Card(modifier = cardModifier) { // ... }}5.2. Cho phép customize từ bên ngoài
Phần tiêu đề “5.2. Cho phép customize từ bên ngoài”@Composablefun 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ụngPrimaryButton( text = "Submit", onClick = { }, modifier = Modifier.fillMaxWidth() // Caller customize)5.3. Best Practices
Phần tiêu đề “5.3. Best Practices”// ✅ ĐÚNG: Modifier parameter ở đầu (sau required params)@Composablefun MyComponent( title: String, // Required modifier: Modifier = Modifier, // Modifier subtitle: String = "" // Optional) { }
// ✅ ĐÚNG: Chain modifier từ caller với internal modifiers@Composablefun CustomCard( modifier: Modifier = Modifier) { Card( modifier = modifier .fillMaxWidth() // Internal modifiers sau caller's .padding(16.dp) ) { }}6. Tạo Custom Modifier
Phần tiêu đề “6. Tạo Custom Modifier”6.1. Extension function đơn giản
Phần tiêu đề “6.1. Extension function đơn giản”// Custom modifier để làm round corners với backgroundfun Modifier.roundedBackground( color: Color, cornerRadius: Dp = 8.dp): Modifier = this .clip(RoundedCornerShape(cornerRadius)) .background(color)
// Sử dụngText( "Custom", modifier = Modifier.roundedBackground(Color.Blue))6.2. Modifier với composed { }
Phần tiêu đề “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
Phần tiêu đề “📝 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
Phần tiêu đề “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?