컴포저블에 크기 · 여백 · 배경 · 동작을 입히는 체인.
순서가 결과를 바꾼다
// 배경이 여백을 포함한다 — 큰 회색 박스
Modifier.background(Gray).padding(16.dp)
// 여백 안쪽에만 배경 — 작은 회색 박스
Modifier.padding(16.dp).background(Gray)
- **왜 — Modifier 는 바깥에서 안으로 순서대로 감싼다**
- background 가 먼저면 '전체 영역' 을 칠하고 그 안에 여백을 준다
- padding 이 먼저면 '여백을 뺀 영역' 이 background 에 전달된다
클릭 영역도 순서를 탄다
Modifier.clickable { }.padding(16.dp) // 여백까지 클릭된다 (터치 영역이 넓다)
Modifier.padding(16.dp).clickable { } // 내용만 클릭된다
접근성 최소 터치 영역은 48dp 다 작은 아이콘은 clickable 을 먼저 걸거나 minimumInteractiveComponentSize 를 쓴다
세 가지 기본 레이아웃
Column { } 세로로 쌓는다
Row { } 가로로 쌓는다
Box { } 겹친다 (z 축)
Column(
verticalArrangement = Arrangement.spacedBy(8.dp), // 자식 사이 간격
horizontalAlignment = Alignment.CenterHorizontally
)
weight — 남는 공간을 나눈다
Row {
Text("고정")
Spacer(Modifier.weight(1f)) // 남는 공간을 밀어낸다
Icon(...)
}
weight 는 Row/Column 스코프 안에서만 쓸 수 있다 (RowScope 확장 함수) fillMaxWidth() 와 weight 를 같이 주면 weight 가 이긴다
재사용 가능한 컴포저블의 규칙
@Composable
fun MyCard(
title: String,
modifier: Modifier = Modifier // ★ 첫 번째 선택 파라미터로 받는다
) {
Card(modifier) { Text(title) } // ★ 받은 것을 최상위에 그대로 적용한다
}
이 규칙을 안 지키면 호출부가 크기·여백을 제어할 수 없다 또 받은 modifier 를 안쪽 자식에 붙이면 호출부의 기대와 어긋난다
Modifier.then과 재사용
val base = Modifier.fillMaxWidth().padding(16.dp)
Box(base.then(Modifier.background(Gray)))
// 조건부 적용
Modifier.then(if (isSelected) Modifier.border(2.dp, Blue) else Modifier)
면접 함정
- ❌ "Modifier 순서는 스타일 취향" → 결과가 실제로 다르다. 여백·배경·클릭 영역이 전부 달라진다.
- ❌
modifier를 받지 않는 공용 컴포저블 → 재사용이 사실상 불가능해진다.
크기 지정의 종류
Modifier.size(48.dp) // 정확히 이 크기 (부모 제약을 무시하고 강제)
Modifier.requiredSize(48.dp) // 부모 제약도 무시한다 (밖으로 넘칠 수 있다)
Modifier.fillMaxWidth(0.5f) // 부모의 50%
Modifier.wrapContentSize() // 내용 크기
Modifier.defaultMinSize(48.dp) // 최소 크기 보장 (더 커질 수 있다)
Modifier.aspectRatio(16f / 9f) // 비율 유지
여러 번 적용하면
Modifier.padding(8.dp).padding(8.dp) // 합쳐서 16dp
Modifier.size(100.dp).size(50.dp) // 첫 번째가 이긴다 (제약이 먼저 좁혀진다)
Modifier 는 '누적' 이 아니라 '중첩' 이다 바깥쪽이 제약을 정하고 안쪽이 그 안에서 다시 정한다
- size 를 두 번 주면 두 번째가 무시되는 것처럼 보인다
커스텀 Modifier
fun Modifier.shimmer(): Modifier = composed {
val transition = rememberInfiniteTransition(label = "shimmer")
val x by transition.animateFloat(0f, 1000f, infiniteRepeatable(tween(1200)), label = "x")
drawWithContent { drawContent(); drawRect(brush = gradientAt(x)) }
}
composed { } 는 성능 비용이 있어 Modifier.Node 방식이 권장된다 간단한 조합이면 그냥 확장 함수로 충분하다
- fun Modifier.card() = this.padding(16.dp).clip(RoundedCornerShape(12.dp))