상단바 · 하단바 · FAB 슬롯을 갖춘 화면 골격.
Scaffold(
topBar = { TopAppBar(title = { Text("홈") }) },
bottomBar = { NavigationBar { ... } },
floatingActionButton = { FloatingActionButton(onClick = {}) { Icon(Add, null) } },
snackbarHost = { SnackbarHost(snackbarHostState) }
) { innerPadding ->
LazyColumn(Modifier.padding(innerPadding)) { ... } // ★ 반드시 소비한다
}
innerPadding을 안 쓰면
내용이 상단바 아래로 파고들어 첫 항목이 가려진다 하단바에 마지막 항목이 잘린다
스크롤 목록이면 contentPadding 으로 주는 편이 낫다
- 스크롤할 때 내용이 바 아래로 자연스럽게 지나간다
- LazyColumn(contentPadding = innerPadding)
Slot API — 내용을 밖에서 넣는다
@Composable
fun MyCard(
modifier: Modifier = Modifier,
header: @Composable () -> Unit,
content: @Composable ColumnScope.() -> Unit
) {
Card(modifier) { Column { header(); content() } }
}
파라미터를 늘려 모든 경우를 지원하려 하면
- MyCard(title, subtitle, icon, iconTint, showBadge, badgeColor, ...)
- 조합 폭발
슬롯으로 열어 두면 호출부가 원하는 것을 그대로 넣는다
- Compose 표준 컴포넌트가 전부 이 방식이다
Edge-to-edge와 인셋
// Android 15+ 는 기본이 edge-to-edge 다
enableEdgeToEdge()
// 시스템 바 영역을 직접 다뤄야 한다
Modifier.windowInsetsPadding(WindowInsets.systemBars)
Modifier.imePadding() // 키보드가 올라오면 밀어 올린다
Scaffold 는 인셋을 계산해 innerPadding 에 포함시켜 준다 직접 Column 으로 화면을 짜면 상태바 뒤에 내용이 깔린다
커스텀 레이아웃
Layout(content = content, modifier = modifier) { measurables, constraints ->
val placeables = measurables.map { it.measure(constraints) }
layout(constraints.maxWidth, placeables.sumOf { it.height }) {
var y = 0
placeables.forEach { it.placeRelative(0, y); y += it.height }
}
}
Compose 는 단일 패스 측정이다 — 자식을 두 번 측정할 수 없다
- (View 시스템의 다중 measure 로 인한 성능 문제를 설계로 막았다)
- intrinsic 측정이 필요하면 별도 API 를 쓴다
면접 함정
- ❌
innerPadding을 무시한다 → 컴파일은 되고 화면만 깨진다. 가장 흔한 Compose 실수다. - ❌ "Scaffold가 인셋을 다 처리한다" → 키보드(
imePadding)는 별도로 다뤄야 하는 경우가 많다.