Bỏ qua để đến nội dung

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.

Modifier là một chuỗi các “decorators” được áp dụng lên composable để:

  • Thay đổi kích thướcvị 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
)
}

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!

// 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ả padding
@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)
)
}
}
  1. Padding ngoài → dùng padding() TRƯỚC background()
  2. Padding trong → dùng padding() SAU background()
  3. Clickable areaclickable() SAU padding() để click cả padding

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 constraints
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)
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 shape
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)) // Shadow
Modifier
.clickable { /* handle click */ }
.clickable(
enabled = true,
onClick = { }
)
.combinedClickable(
onClick = { },
onLongClick = { },
onDoubleClick = { }
)
.selectable(selected = isSelected, onClick = { })
.toggleable(value = isOn, onValueChange = { })
Modifier
.verticalScroll(rememberScrollState())
.horizontalScroll(rememberScrollState())
.scrollable(
state = rememberScrollableState { delta -> delta },
orientation = Orientation.Vertical
)
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/Column
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:

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ể
}
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))
}
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))
}

// 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) {
// ...
}
}
@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
)
// ✅ ĐÚ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)
) { }
}

// 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)
)

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) }
}

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
  • 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?