
이전 글에서는 버튼 클릭, 텍스트 입력, 제스처 인식 등 다양한 사용자 입력 이벤트를 처리하는 방법을 배웠습니다. 개별 컴포저블들을 다루는 방법을 익혔으니, 이제는 이들을 조합해서 실제 앱처럼 보이는 완성된 화면을 구성해볼 차례입니다.
Text나 Button만으로도 앱을 만들 수 있지만, 많은 상용 앱들을 보면 상단 앱바, 하단 네비게이션, 플로팅 액션 버튼 등 표준적인 UI 구조를 활용하고 있습니다. 이번 글에서는 Jetpack Compose에서 이런 앱의 기본 골격을 구성하는 핵심 도구인 Scaffold를 중심으로, 체계적이고 일관성 있는 화면 레이아웃을 만드는 방법을 알아보겠습니다.
Scaffold는 앱 화면의 기본 구조를 제공하는 컴포저블입니다. 건축에서 '발판, 골조'를 의미하는 단어 그대로, UI의 골격 역할을 담당합니다.
기존 View 시스템에서는 Activity의 레이아웃을 XML로 정의하고, AppBar, Navigation Drawer, BottomNavigation 등을 각각 별도로 설정해야 했습니다. 하지만 Compose의 Scaffold는 이런 공통 UI 요소들을 한 곳에서 선언적으로 배치할 수 있게 해줍니다.
innerPadding으로 콘텐츠 영역이 다른 UI 요소와 겹치지 않도록 보장Scaffold는 다음과 같은 주요 슬롯(slot)들을 제공합니다.
| 슬롯 | 설명 | 주요 용도 |
|---|---|---|
topBar | 화면 상단 앱바 | 제목, 뒤로가기 버튼, 메뉴 아이콘 |
bottomBar | 화면 하단 바 | 탭 네비게이션, 액션 버튼들 |
floatingActionButton | 플로팅 액션 버튼 | 주요 액션 (글쓰기, 추가 등) |
snackbarHost | 스낵바 표시 영역 | 알림, 피드백 메시지 |
content | 메인 콘텐츠 영역 | 실제 화면 내용 |
이제 Scaffold의 구성 요소들을 하나씩 추가해가며 시작해보겠습니다.

import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@Composable
fun ScaffoldExample() {
Scaffold(
topBar = {
TopAppBar(
title = { Text("My App") },
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer
)
)
}
) { innerPadding ->
Text(
text = "메인 콘텐츠 영역입니다",
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
)
}
}
코드 해설
topBar에 TopAppBar를 배치하여 상단에 제목을 표시title 파라미터에는 주로 Text 컴포저블이 사용되지만, @Composable 함수이므로 Row, Icon, Image 등 다양한 컴포저블을 활용한 자유로운 구성도 가능innerPadding은 Scaffold가 자동으로 계산해주는 패딩값으로, TopAppBar 높이만큼 콘텐츠가 밀려나지 않도록 보정해줌padding(innerPadding)을 적용하면 콘텐츠가 TopAppBar와 겹치지 않게 됨💡 innerPadding이 중요한 이유
Scaffold를 사용할 때 반드시innerPadding을 콘텐츠에 적용해야 합니다. 그렇지 않으면 TopAppBar나 BottomBar와 내용이 겹쳐서 보이게 됩니다.
TopAppBar는 다양한 옵션으로 커스터마이징할 수 있습니다.

import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.TopAppBarDefaults
@Composable
fun ScaffoldExample() {
Scaffold(
topBar = {
TopAppBar(
title = { Text("My App") },
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer
),
navigationIcon = {
IconButton(onClick = {/* 뒤로가기 */ }) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "뒤로가기"
)
}
},
actions = {
IconButton(onClick = {/* 검색 */ }) {
Icon(
imageVector = Icons.Default.Search,
contentDescription = "검색"
)
}
IconButton(onClick = {/* 메뉴 */ }) {
Icon(
imageVector = Icons.Default.MoreVert,
contentDescription = "메뉴"
)
}
}
)
}
) { innerPadding ->
Text(
text = "뒤로가기와 액션 버튼이 있는 앱바입니다",
modifier = Modifier.padding(innerPadding)
)
}
}
코드 해설
위 코드에서는 기본 TopAppBar에 두 가지 구성 요소를 추가했습니다.
navigationIcon: 앱바 왼쪽에 배치되는 아이콘으로, 주로 뒤로가기나 메뉴(햄버거) 버튼으로 사용됩니다. IconButton으로 감싸서 클릭 이벤트를 처리할 수 있습니다.actions: 앱바 오른쪽에 배치되는 액션 버튼들입니다. 검색, 메뉴, 설정 등 현재 화면에서 수행할 수 있는 주요 기능들을 배치합니다. 여러 개의 IconButton을 나열할 수 있으며, 공간이 부족하면 자동으로 오버플로우 메뉴로 처리됩니다.이제 하단 바와 플로팅 액션 버튼을 추가해 좀 더 완성된 앱 구조를 만들어보겠습니다.

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.Share
import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@Composable
fun ScaffoldExample() {
Scaffold(
topBar = {
TopAppBar(
title = { Text("My App (문서 뷰어)") },
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer
),
)
},
bottomBar = {
BottomAppBar {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
IconButton(onClick = {/* 편집 */ }) {
Icon(Icons.Default.Edit, contentDescription = "편집")
}
IconButton(onClick = {/* 공유 */ }) {
Icon(Icons.Default.Share, contentDescription = "공유")
}
IconButton(onClick = {/* 삭제 */ }) {
Icon(Icons.Default.Delete, contentDescription = "삭제")
}
}
}
},
floatingActionButton = {
FloatingActionButton(
onClick = {/* 새 문서 추가 */ }
) {
Icon(Icons.Default.Add, contentDescription = "새 문서")
}
}
) { innerPadding ->
Text(
text = "TopBar, BottomBar, FAB가 모두 있는 구조입니다.\n현재 문서에 대한 액션들이 BottomAppBar에 배치되어 있습니다",
modifier = Modifier.padding(innerPadding)
)
}
}
코드 해설
위 코드에서는 BottomAppBar와 FloatingActionButton이 추가되었습니다.
BottomAppBar: 현재 화면(문서)에서 수행할 수 있는 액션들을 배치 - 편집, 공유, 삭제 등BottomAppBar + Row 조합: 하단 바 내부에 Row를 배치하고 Arrangement.SpaceEvenly를 적용해 아이콘 버튼들을 화면 전체 너비에 걸쳐 균등하게 분산 배치fillMaxWidth(): Row가 BottomAppBar의 전체 너비를 사용하도록 설정FloatingActionButton: 화면의 주요 액션(추가)을 담당하는 플로팅 버튼으로, BottomAppBar 위쪽에 자동으로 배치됨구성 요소별 상세 역할
| 구성 요소 | 특징 | 사용 예시 | 배치 규칙 |
|---|---|---|---|
BottomAppBar + Row | 하단 고정 툴바, 균등 분산 배치 | 편집, 공유, 삭제, 즐겨찾기 등 현재 콘텐츠 관련 액션 | 화면 하단에 고정, 여러 버튼 배치 가능 |
FloatingActionButton | 주요 액션 버튼 | 새 글쓰기, 사진 촬영, 전화걸기 등 핵심 기능 | 기본적으로 우하단 배치, BottomBar 위로 자동 조정 |
구성 요소별 상세 역할
레이아웃 자동 조정 기능
하단 영역에는 두 가지 주요 선택지가 있으며, 각각은 서로 다른 목적과 사용 사례를 가지고 있습니다. 어떤 것을 선택할지는 앱의 구조와 사용자 경험 설계에 따라 달라집니다.
BottomAppBar는 2-3에서 본 예시와 같이, 현재 화면에서 수행할 수 있는 액션들을 배치하는 영역입니다. 사진 편집 앱의 자르기-필터-조정-저장 버튼이나, 문서 뷰어의 공유-북마크-검색-인쇄 같은 기능들을 생각하면 됩니다.

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.Share
import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
@Composable
fun ScaffoldExample() {
Scaffold(
bottomBar = {
BottomAppBar {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly,
) {
IconButton(onClick = { /* 편집 */ }) {
Icon(Icons.Default.Edit, contentDescription = "편집")
}
IconButton(onClick = { /* 공유 */ }) {
Icon(Icons.Default.Share, contentDescription = "공유")
}
IconButton(onClick = { /* 삭제 */ }) {
Icon(Icons.Default.Delete, contentDescription = "삭제")
}
}
}
},
) { innerPadding ->
Box(
modifier =
Modifier
.fillMaxSize()
.padding(innerPadding),
contentAlignment = Alignment.Center,
) {
Text(
text = "현재 화면에서 수행할 수 있는\n액션을 배치하는 BottomAppBar 예시입니다.",
textAlign = TextAlign.Center,
)
}
}
}
BottomAppBar의 특징과 장점
Row와 Arrangement를 활용해 버튼 배치를 자유롭게 조정BottomAppBar 구성 방법
BottomAppBar 내부에 여러 IconButton을 배치Row와 Arrangement.SpaceEvenly로 버튼들을 균등하게 분산 배치IconButton 대신 TextButton 사용 가능활용 팁
enabled 속성을 활용해 특정 조건에서만 버튼을 활성화할 수 있습니다NavigationBar는 여러 화면 간 이동을 위한 탭 구조로, 앱의 주요 섹션들을 빠르게 전환할 수 있게 해줍니다. Instagram의 홈-검색-릴스-쇼핑-프로필이나 Twitter의 홈-검색-알림-메시지 같은 구조를 생각하면 됩니다.

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
@Composable
fun ScaffoldExample() {
var selectedTab by remember { mutableStateOf(0) }
Scaffold(
bottomBar = {
NavigationBar {
NavigationBarItem(
icon = { Icon(Icons.Default.Home, contentDescription = null) },
label = { Text("홈") },
selected = selectedTab == 0,
onClick = { selectedTab = 0 }
)
NavigationBarItem(
icon = { Icon(Icons.Default.Favorite, contentDescription = null) },
label = { Text("즐겨찾기") },
selected = selectedTab == 1,
onClick = { selectedTab = 1 }
)
NavigationBarItem(
icon = { Icon(Icons.Default.Settings, contentDescription = null) },
label = { Text("설정") },
selected = selectedTab == 2,
onClick = { selectedTab = 2 }
)
}
}
) { innerPadding ->
Box(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding),
contentAlignment = Alignment.Center
) {
when (selectedTab) {
0 -> Text(
text = "홈 화면",
fontSize = 24.sp,
fontWeight = FontWeight.Bold
)
1 -> Text(
text = "즐겨찾기 화면",
fontSize = 24.sp,
fontWeight = FontWeight.Bold
)
2 -> Text(
text = "설정 화면",
fontSize = 24.sp,
fontWeight = FontWeight.Bold
)
}
}
}
}
NavigationBar의 특징과 장점
selected 속성으로 현재 활성화된 탭을 시각적으로 강조selectedTab 상태 하나로 전체 화면 전환 제어NavigationBarItem 구성 요소
icon: 각 탭을 나타내는 아이콘 (필수)label: 탭 이름을 표시하는 텍스트 (선택사항, 하지만 접근성을 위해 권장)selected: 현재 선택된 탭인지 여부를 나타내는 Boolean 값onClick: 탭 클릭 시 실행될 동작활용 팁
when 표현식을 사용해 선택된 탭에 따라 다른 컴포저블을 표시할 수 있습니다지금까지 배운 내용을 종합해서 실제 앱처럼 보이는 프로필 카드 앱의 메인 화면을 만들어보겠습니다.
이번 예제에서는 다음과 같은 기능을 구현해볼 예정입니다.
이를 통해 Scaffold의 모든 구성 요소를 활용해보고, 이전 글에서 배운 기본 컴포저블들을 실제로 조합하는 경험을 할 수 있습니다.

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.FavoriteBorder
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Share
import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.composebeginnerguide.R
@Composable
fun ScaffoldExample() {
Scaffold(
topBar = {
TopAppBar(
title = {
Text(
"내 프로필",
fontWeight = FontWeight.Bold
)
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer
),
actions = {
IconButton(onClick = { /* 메뉴 */ }) {
Icon(
imageVector = Icons.Default.MoreVert,
contentDescription = "메뉴"
)
}
}
)
},
bottomBar = {
BottomAppBar {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
IconButton(onClick = { /* 편집 */ }) {
Icon(Icons.Default.Edit, contentDescription = "편집")
}
IconButton(onClick = { /* 공유 */ }) {
Icon(Icons.Default.Share, contentDescription = "공유")
}
IconButton(onClick = { /* 좋아요 */}) {
Icon(Icons.Default.FavoriteBorder, contentDescription = "좋아요")
}
}
}
},
floatingActionButton = {
FloatingActionButton(
onClick = { /* 프로필 수정 */ }
) {
Icon(Icons.Default.Edit, contentDescription = "프로필 수정")
}
}
) { innerPadding ->
Box(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding),
contentAlignment = Alignment.Center
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.padding(24.dp)
) {
Image(
painter = painterResource(id = R.drawable.ic_launcher_background),
contentDescription = "프로필 사진",
modifier = Modifier
.size(120.dp)
.clip(CircleShape),
contentScale = ContentScale.Crop
)
Text(
text = "김개발자",
fontSize = 24.sp,
fontWeight = FontWeight.Bold
)
Text(
text = "Android Developer",
fontSize = 16.sp,
color = Color.Gray
)
Text(
text = "Jetpack Compose를 좋아합니다.\n새로운 기술을 배우고 성장하려고 노력합니다!",
fontSize = 14.sp,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 16.dp)
)
Row(
horizontalArrangement = Arrangement.spacedBy(32.dp)
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = "128",
fontSize = 20.sp,
fontWeight = FontWeight.Bold
)
Text(
text = "팔로워",
fontSize = 12.sp,
color = Color.Gray
)
}
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = "42",
fontSize = 20.sp,
fontWeight = FontWeight.Bold
)
Text(
text = "좋아요",
fontSize = 12.sp,
color = Color.Gray
)
}
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = "45",
fontSize = 20.sp,
fontWeight = FontWeight.Bold
)
Text(
text = "게시물",
fontSize = 12.sp,
color = Color.Gray
)
}
}
}
}
}
}
Scaffold: 앱 화면의 기본 레이아웃을 구성하며 TopAppBar, BottomAppBar, FAB, 콘텐츠 영역을 함께 선언함TopAppBar: 앱 제목과 메뉴 아이콘을 포함한 상단 앱바를 구성함BottomAppBar: 편집, 공유, 좋아요 등 현재 프로필과 관련된 액션 버튼들을 균등하게 배치함FloatingActionButton: 프로필 수정 기능을 담당하며 화면 우측 하단에 배치됨Image + CircleShape: 원형 프로필 이미지를 표시함Column + Row: 텍스트와 통계 정보를 정렬하여 화면 중앙에 배치함Box: 전체 콘텐츠를 가운데 정렬하는 데 사용함Modifier.padding(innerPadding): TopAppBar와 BottomAppBar에 겹치지 않도록 콘텐츠 영역의 여백을 자동으로 조정함innerPadding을 사용하면 콘텐츠가 TopBar/BottomBar와 겹치지 않습니다이번 글에서는 Jetpack Compose에서 앱 화면의 기본 구조를 잡아주는 핵심 컴포저블인 Scaffold를 집중적으로 살펴봤습니다. Scaffold를 사용하면 다음과 같은 장점이 있습니다.
또한 Scaffold의 핵심 슬롯(slot)들을 통해 다음의 UI 요소들을 배치했습니다.
| 슬롯 | 설명 | 주요 용도 |
|---|---|---|
topBar | 화면 상단 앱바 | 제목, 뒤로가기 버튼, 액션 메뉴 |
bottomBar | 화면 하단 바 | 탭 네비게이션, 액션 버튼들 |
floatingActionButton | 플로팅 액션 버튼 | 새 글쓰기, 추가 등 핵심 액션 |
snackbarHost | 스낵바 표시 영역 | 알림, 피드백 메시지 |
content | 메인 콘텐츠 영역 | 실제 화면 콘텐츠 |
이를 통해 프로필 화면 구성 예제를 실습하면서 Scaffold의 실제 활용 방법을 익혔습니다.
또한 하단 UI를 구성할 때, NavigationBar와 BottomAppBar의 차이를 명확히 구분하고 사용 목적과 선택 기준을 설명했습니다.
Compose로 화면 구조를 관리할 때 Scaffold를 제대로 활용하면 복잡한 UI 구성도 깔끔하고 유지보수하기 쉽게 만들어줄 수 있습니다.
이번 글을 통해 앱의 화면을 구성하는 방법을 익혔습니다. 다음 글에서는 Jetpack Compose 앱에서 여러 화면 간의 전환과 내비게이션 구조를 구성하는 방법을 배워볼 예정입니다.
Jetpack Compose에서 제공하는 NavHost와 NavController를 이용해 어떻게 화면 전환을 구현하고, 화면 간 데이터 전달 및 상태 관리를 할 수 있는지 단계적으로 학습할 예정입니다. 하나의 앱에서 여러 화면을 연결하고, 사용자의 이동 흐름을 어떻게 제어할 수 있는지 알아가보도록 합시다! 기대해주세요! 🚀