
컴포즈에서는 프래그먼트라는 화면 단위가 없고
xml기반에서의 화면 이동과는 다른 개념
다른 형태의 화면으로 재구성해줌
하나의 화면 단위를 Screen이라고 부른다(Fragment와 같이 별개의 클래스가 있는 건 아님)
라이브러리를 추가해준다
implementation("androidx.navigation.navigation-compose:2.7.7")
MainActivity는 처음은 아래와 같이 지워준다.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
JetPackExampleTheme {
}
}
}
}
각 화면 이름을 저장하는 enum class를 만들어준다.
// 각 화면을 저장할 이름
enum class ScreenName(){
MainScreen,
InputScreen,
OutputScreen
}
화면 네비게이션을 관리하는 객체를 생성하고 보여줄 화면을 등록해준다
// 애플리케이션 메인 코드
@Composable
fun MemoApp(modifier: Modifier = Modifier){
// 화면 네비게이션을 관리하는 객체
val navController = rememberNavController()
// 보여줄 화면을 등록한다.
NavHost(
// 화면 네비게이션을 관리하는 객체
navController = navController,
// 첫 화면의 이름
startDestination = ScreenName.MainScreen.name){
// 사용할 화면을 등록해준다.
// 메인 화면
composable(
route = ScreenName.MainScreen.name
){
}
// 입력 화면
composable(
route = ScreenName.InputScreen.name
){
}
// 결과 화면
composable(
route = ScreenName.OutputScreen.name
){
}
}
}
route는 화면 이름을 지정해주며, 새로운 화면을 보여주고 싶을 때 지정하는 이름이 route에 등록되어 있는 화면을 보여주게 된다.
이와 같이 MainActivity에서는 화면 별 이름을 지정하고 네비게이션 컨트롤러를 만든 다음에 화면 별로 준비한 코드가 동작하도록 해주기만 하면 된다.
새로운 화면을 만들 때에는 kt파일을 만들어준다.
import androidx.compose.runtime.Composable
@Composable
fun MainScreen(navHostController: NavHostController) {
}
또한 Material 3를 사용할 때는 실험적 요소를 사용하기 위한 어노테이션을 붙여준다
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.navigation.NavHostController
// Material3가 아직 완성 버전이 아니기 때문에
// 실험적 요소(미완성된 UI 요소)를 사용할 때 반드시 붙여줘야 하는 어노테이션
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScreen(navHostController: NavHostController) {
}
상단 툴바는 아래와 같이 배치한다.
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScreen(navHostController: NavHostController) {
Scaffold(
// 상단 툴바
topBar = {
TopAppBar(
// 툴바에 표시될 타이틀
title = {
Text(text = "메모 목록")
},
// 툴바의 색상
colors = TopAppBarDefaults.topAppBarColors(
// 툴바 색상
containerColor = Color.White,
// 툴바의 타이틀 색상
titleContentColor = Color.Black
)
)
}
) {
}
}
화면 본문의 레이아웃은 Column으로 아래와 같이 설정한다.
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScreen(navHostController: NavHostController) {
Scaffold(
// 상단 툴바
topBar = {
TopAppBar(
// 툴바에 표시될 타이틀
title = {
Text(text = "메모 목록")
},
// 툴바의 색상
colors = TopAppBarDefaults.topAppBarColors(
// 툴바 색상
containerColor = Color.White,
// 툴바의 타이틀 색상
titleContentColor = Color.Black
)
)
}
) {
// 위에서 아래방향으로 배치하는 레이아웃
Column(
// fillMaxSize : 화면의 크기를 단말기 전체 화면으로 설정한다.
// padding : 여백. Scaffold의 it 안에는 상단 툴바 만큼의 여백이 설정되어 있다.
// background : 배경 색상
modifier = Modifier.fillMaxSize().padding(it).background(Color.White)
) {
}
}
}
다시 MainActivity로 돌아가 MainScreen이 구성되도록 셋팅해준다.
// 메인 화면
composable(
route = ScreenName.MainScreen.name
){
// MainScreen이 구성되도록 호출한다.
MainScreen(navHostController = navController)
}
실행을 하면 아래와 같이 메인 화면이 나오는 것을 확인할 수 있다.

두번째 화면도 만들어준다.
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
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
import androidx.compose.ui.graphics.Color
import androidx.navigation.NavHostController
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun InputScreen(navHostController: NavHostController) {
Scaffold(
// 상단 툴바
topBar = {
TopAppBar(
// 툴바에 표시될 타이틀
title = {
Text(text = "메모 등록")
},
// 툴바의 색상
colors = TopAppBarDefaults.topAppBarColors(
// 툴바 색상
containerColor = Color.White,
// 툴바의 타이틀 색상
titleContentColor = Color.Black
),
// 네비게이션 아이콘
navigationIcon = {
IconButton(onClick = { /*TODO*/ }) {
Icon(
// 표시할 아이콘
imageVector = Icons.Filled.ArrowBack,
// 설명 문자열(화면에 나타나지는 않음)
contentDescription = "뒤로가기",
// 아이콘 색상
tint = Color.Black
)
}
}
)
}
) {
// 위에서 아래방향으로 배치하는 레이아웃
Column(
// fillMaxSize : 화면의 크기를 단말기 전체 화면으로 설정한다.
// padding : 여백. Scaffold의 it 안에는 상단 툴바 만큼의 여백이 설정되어 있다.
// background : 배경 색상
modifier = Modifier
.fillMaxSize()
.padding(it)
.background(Color.White)
) {
}
}
}
메인 화면에서 입력 화면으로 전환할 수 있도록 버튼을 추가해준다
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScreen(navHostController: NavHostController) {
Scaffold(
// 상단 툴바
topBar = {
TopAppBar(
// 툴바에 표시될 타이틀
title = {
Text(text = "메모 목록")
},
// 툴바의 색상
colors = TopAppBarDefaults.topAppBarColors(
// 툴바 색상
containerColor = Color.White,
// 툴바의 타이틀 색상
titleContentColor = Color.Black
),
// 툴바의 메뉴
actions = {
IconButton(
onClick = {
// InputScreen이 보여지도록 한다.
navHostController.navigate(ScreenName.InputScreen.name)
}
) {
Icon(
imageVector = Icons.Filled.Add,
contentDescription = "메모 추가",
tint = Color.Black
)
}
}
)
}
)
InputScreen으로 전환해주는 코드를 설정해준다.
// 애플리케이션 메인 코드
@Composable
fun MemoApp(modifier: Modifier = Modifier){
// 화면 네비게이션을 관리하는 객체
val navController = rememberNavController()
// 보여줄 화면을 등록한다.
NavHost(
// 화면 네비게이션을 관리하는 객체
navController = navController,
// 첫 화면의 이름
startDestination = ScreenName.MainScreen.name){
// 사용할 화면을 등록해준다.
// route : 화면 이름을 지정한다.
// 새로운 화면을 보여주고 싶을 때 지정하는 이름이 route에 등록되어 있는 화면을 보여주게 된다.
// 메인 화면
composable(
route = ScreenName.MainScreen.name
){
// MainScreen이 구성되도록 호출한다.
MainScreen(navHostController = navController)
}
// 입력 화면
composable(
route = ScreenName.InputScreen.name
){
// InputScreen이 구성되도록 호출한다.
InputScreen(navHostController = navController)
}
// 결과 화면
composable(
route = ScreenName.OutputScreen.name
){
}
}
}
실행해서 메인화면에서 등록 버튼을 눌러주면 입력 화면이 나타난다.


뒤로가기 기능은 네비게이션 컨트롤러에서 popBackStack()을 호출하면 된다.
// 네비게이션 아이콘
navigationIcon = {
IconButton(
onClick = {
// 이전 화면이 보이도록 한다.
navHostController.popBackStack()
}
) {
Icon(
// 표시할 아이콘
imageVector = Icons.Filled.ArrowBack,
// 설명 문자열(화면에 나타나지는 않음)
contentDescription = "뒤로가기",
// 아이콘 색상
tint = Color.Black
)
}
}
화면 전환 애니메이션은 아래와 같이 설정할 수 있다.
// 메인 화면
composable(
route = ScreenName.MainScreen.name,
// 화면 전환 애니메이션 설정
// https://developer.android.com/develop/ui/compose/animation/composables-modifiers?hl=ko
// A 화면에서 B화면으로 이동한다고 가정한다.
// 다음 화면으로 전환 될 때 B 화면에 적용되는 애니메이션
enterTransition = {
slideInHorizontally(
// 화면의 초기 위치
initialOffsetX = {it},
// 애니메이션 부가 설정
// durationMillis : 애니메이션 동작 시간
// delayMillis : 애니메이션 대기 시간
animationSpec = tween(
durationMillis = 200,
delayMillis = 200
)
)
},
// 다음 화면으로 전환 될 때 A 화면에 적용되는 애니메이션
exitTransition = {
// slideOutHorizontally()
fadeOut(
animationSpec = tween(
durationMillis = 200,
delayMillis = 200
)
)
},
// 다음 화면에서 돌아올 때 A 화면에 적용되는 애니메이션
popEnterTransition = {
// slideInHorizontally()
fadeIn(
animationSpec = tween(
durationMillis = 200,
delayMillis = 200
)
)
},
// 다음 화면에서 돌아올 때 B 화면에 적용되는 애니메이션
popExitTransition = {
slideOutHorizontally(
targetOffsetX = {it},
animationSpec = tween(
durationMillis = 200,
delayMillis = 200
)
)
}
){
// MainScreen이 구성되도록 호출한다.
MainScreen(navHostController = navController)
}
나머지 화면에도 동일하게 애니메이션을 적용해주면 된다.
참고 링크
https://developer.android.com/develop/ui/compose/animation/composables-modifiers?hl=ko
컴포즈에서는 리사이클러뷰 대신 lazyColumn을 사용한다.
// Column은 위에서 아래 방향으로 화면 요소들을 배치하기 위해 사용한다.
// Row는 좌측에서 우측 방향으로 화면 요소들을 배치하기 위해 사용한다.
// Box는 겹쳐지게 화면 요소들을 배치하기 위해 사용한다.
// 위의 3개는 배치한 화면 요소들이 단말기 화면 밖으로 벗어난다고 해도 모두 생성된다.
// Lazy로 시작하는 것들도 위와 유사하지만 화면 상에 보이지 않는 화면 요소들은
// 생성이 대기하고 있다가 보이게 되는 순간에 생성된다.
// 보였다가 사라진 요소들은 사용 대기상태가 되고 재사용된다.(리사이클러뷰와 동일)
LazyColumn{
// 리스트
// 100개의 항목을 가진 리스트를 생성한다.
// LazyColumn 안에 있기 때문에 보이지 않는 항목들은 생성이 대기
// 사라진 항목들은 새롭게 나타난 항목들을 위해 재사용된다.
// it에는 몇번째 항목인지 값이 들어온다
items(100){
// 항목 하나의 모양을 구성한다.
Column(
modifier = Modifier
.padding(10.dp)
.fillMaxSize()
) {
Text(text = "항목 입니다 : $it")
}
Divider()
}
}

항목을 클릭하면 내용을 보여줄 화면을 만들어준다
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ResultScreen(navHostController: NavHostController) {
Scaffold(
// 상단 툴바
topBar = {
TopAppBar(
// 툴바에 표시될 타이틀
title = {
Text(text = "메모 목록")
},
// 툴바의 색상
colors = TopAppBarDefaults.topAppBarColors(
// 툴바 색상
containerColor = Color.White,
// 툴바의 타이틀 색상
titleContentColor = Color.Black
),
// 네비게이션 아이콘
navigationIcon = {
IconButton(
onClick = {
// 이전 화면이 보이도록 한다.
navHostController.popBackStack()
}
) {
Icon(
// 표시할 아이콘
imageVector = Icons.Filled.ArrowBack,
// 설명 문자열(화면에 나타나지는 않음)
contentDescription = "뒤로가기",
// 아이콘 색상
tint = Color.Black
)
}
}
)
}
) {
// 위에서 아래방향으로 배치하는 레이아웃
Column(
// fillMaxSize : 화면의 크기를 단말기 전체 화면으로 설정한다.
// padding : 여백. Scaffold의 it 안에는 상단 툴바 만큼의 여백이 설정되어 있다.
// background : 배경 색상
modifier = Modifier
.fillMaxSize()
.padding(it)
.background(Color.White)
) {
Text(text = "제목 : 제목입니다")
// 여백
Spacer(modifier = Modifier.padding(top = 10.dp))
Text(text = "내용 : 내용입니다")
}
}
}
메인 화면에서 항목을 눌렀을 때 결과 화면이 보여지도록 한다
items(100){
// 항목 하나의 모양을 구성한다.
Column(
modifier = Modifier
.padding(10.dp)
.fillMaxSize()
// 항목을 눌렀을 때
.clickable {
// 결과 화면이 보이도록 한다.
navHostController.navigate(ScreenName.OutputScreen.name)
}
) {
Text(text = "항목 입니다 : $it")
}
Divider()
}
네비게이션 컨트롤러의 결과 화면 부분을 셋팅한다
// 결과 화면
composable(
route = ScreenName.OutputScreen.name,
// 화면 전환 애니메이션 설정
// https://developer.android.com/develop/ui/compose/animation/composables-modifiers?hl=ko
// A 화면에서 B화면으로 이동한다고 가정한다.
// 다음 화면으로 전환 될 때 B 화면에 적용되는 애니메이션
enterTransition = {
slideInHorizontally(
// 화면의 초기 위치
initialOffsetX = {it},
// 애니메이션 부가 설정
// durationMillis : 애니메이션 동작 시간
// delayMillis : 애니메이션 대기 시간
animationSpec = tween(
durationMillis = 200,
delayMillis = 200
)
)
},
// 다음 화면으로 전환 될 때 A 화면에 적용되는 애니메이션
exitTransition = {
// slideOutHorizontally()
fadeOut(
animationSpec = tween(
durationMillis = 200,
delayMillis = 200
)
)
},
// 다음 화면에서 돌아올 때 A 화면에 적용되는 애니메이션
popEnterTransition = {
// slideInHorizontally()
fadeIn(
animationSpec = tween(
durationMillis = 200,
delayMillis = 200
)
)
},
// 다음 화면에서 돌아올 때 B 화면에 적용되는 애니메이션
popExitTransition = {
slideOutHorizontally(
targetOffsetX = {it},
animationSpec = tween(
durationMillis = 200,
delayMillis = 200
)
)
}
){
// ResultScreen이 구성되도록 호출한다.
ResultScreen(navHostController = navController)
}


입력 화면은 아래와 같이 구성한다.
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun InputScreen(navHostController: NavHostController) {
Scaffold(
// 상단 툴바
topBar = {
TopAppBar(
// 툴바에 표시될 타이틀
title = {
Text(text = "메모 등록")
},
// 툴바의 색상
colors = TopAppBarDefaults.topAppBarColors(
// 툴바 색상
containerColor = Color.White,
// 툴바의 타이틀 색상
titleContentColor = Color.Black
),
// 네비게이션 아이콘
navigationIcon = {
IconButton(
onClick = {
// 이전 화면이 보이도록 한다.
navHostController.popBackStack()
}
) {
Icon(
// 표시할 아이콘
imageVector = Icons.Filled.ArrowBack,
// 설명 문자열(화면에 나타나지는 않음)
contentDescription = "뒤로가기",
// 아이콘 색상
tint = Color.Black
)
}
},
// 우측 상단 메뉴
actions = {
IconButton(
onClick = {
navHostController.popBackStack()
}
) {
Icon(
imageVector = Icons.Filled.Done,
contentDescription = "완료",
tint = Color.Black
)
}
}
)
}
) {
// 위에서 아래방향으로 배치하는 레이아웃
Column(
// fillMaxSize : 화면의 크기를 단말기 전체 화면으로 설정한다.
// padding : 여백. Scaffold의 it 안에는 상단 툴바 만큼의 여백이 설정되어 있다.
// background : 배경 색상
modifier = Modifier
.fillMaxSize()
.padding(it)
.background(Color.White)
) {
// 제목 입력 요소와 연결되어 있는 데이터 관리 요소
val subjectTextState = remember {
mutableStateOf("")
}
// 제목 입력 요소
TextField(
// TextField가 관리하는 값
// 데이터 관리 요소를 지정해준다.
value = subjectTextState.value,
// 사용자가 입력한 값이 변경되었을 때
onValueChange ={
subjectTextState.value = it
},
// hint
placeholder = {
Text(text = "제목을 입력해주세요")
},
// 한 줄 입력 요소로 설정한다.
singleLine = true,
// 가로 길이
modifier = Modifier.fillMaxWidth(),
// 색상
colors = TextFieldDefaults.colors(
// 포커스가 주어졌을 때의 색상(투명색으로 설정)
focusedContainerColor = Color.Transparent,
// 포커스가 사렺ㅆ을 때의 배경 색상(투명색으로 설정)
unfocusedContainerColor = Color.Transparent,
),
// 좌측의 아이콘
leadingIcon = {
Icon(
imageVector = Icons.Filled.AccountCircle,
contentDescription = "제목",
)
},
// 우측의 아이콘
trailingIcon = {
IconButton(
onClick = {
// 제목 TextField에 연결되어 있는 데이터 관리요소에
// 길이가 0인 문자열을 넣어준다.
subjectTextState.value = ""
}
) {
Icon(imageVector = Icons.Filled.Clear, contentDescription = "초기화")
}
}
)
// 여백
Spacer(modifier = Modifier.padding(top = 10.dp))
// 내용 입력 요소와 연결되어 있는 데이터 관리 요소
val contentTextState = remember {
mutableStateOf("")
}
// 제목 입력 요소
TextField(
// TextField가 관리하는 값
// 데이터 관리 요소를 지정해준다.
value = contentTextState.value,
// 사용자가 입력한 값이 변경되었을 때
onValueChange ={
contentTextState.value = it
},
// hint
placeholder = {
Text(text = "내용을 입력해주세요")
},
// 가로 길이
modifier = Modifier.fillMaxWidth(),
// 색상
colors = TextFieldDefaults.colors(
// 포커스가 주어졌을 때의 색상(투명색으로 설정)
focusedContainerColor = Color.Transparent,
// 포커스가 사렺ㅆ을 때의 배경 색상(투명색으로 설정)
unfocusedContainerColor = Color.Transparent,
),
// 좌측의 아이콘
leadingIcon = {
Icon(
imageVector = Icons.Filled.AccountBox,
contentDescription = "내용",
)
},
// 우측의 아이콘
trailingIcon = {
IconButton(
onClick = {
// 제목 TextField에 연결되어 있는 데이터 관리요소에
// 길이가 0인 문자열을 넣어준다.
contentTextState.value = ""
}
) {
Icon(imageVector = Icons.Filled.Clear, contentDescription = "초기화")
}
}
)
}
}
}
