![]() |
![]() |
1. Nav.Controller - (Central API) navigation이 수행할 내용을 정의
navigation.navigate(route)
2. Nav.Host - 개별 navigation graph item을 host 함
사용자가 새로운 화면으로 navigate 할 때 navHost는 개별 destination(composable)을 교체 함
3. Navigation Graph - destination, screen, composable 관련 정보를 보관함
NavGraph는 destination, screen, composable 와 관련 정보 모두를 map out 한다.
프로젝트에 우선 navigation, screen package를 만들고 작업
======================================================================
navigation 시에 특정 영역 클릭 시에 해당 값을 받아서 navigation에서 이 값을 활용 특정 페이지로 이동하는 방법
1. data class 생성
data class Movie(val id: String,
val title: String,
val year: String,
val genre: String,
val director: String,
val actors: String,
val plot: String,
val poster: String,
val images: List<String>,
val rating: String)
2. data list 생성
fun getMovies(): List<Movie> {
return listOf(
Movie(id = idList.uuidList[0],
title = "Avatar",
year = "2009",
genre = "Action, Adventure, Fantasy",
director = "James Cameron",
actors = "Sam Worthington, Zoe Saldana, Sigourney Weaver, Stephen Lang",
plot = "A paraplegic marine dispatched to the moon Pandora on a unique mission becomes torn between following his orders and protecting the world he feels is his home.",
poster = "http://ia.media-imdb.com/images/M/MV5BMTYwOTEwNjAzMl5BMl5BanBnXkFtZTcwODc5MTUwMw@@._V1_SX300.jpg",
images = listOf("https://images-na.ssl-images-amazon.com/images/M/MV5BMjEyOTYyMzUxNl5BMl5BanBnXkFtZTcwNTg0MTUzNA@@._V1_SX1500_CR0,0,1500,999_AL_.jpg",
"https://images-na.ssl-images-amazon.com/images/M/MV5BNzM2MDk3MTcyMV5BMl5BanBnXkFtZTcwNjg0MTUzNA@@._V1_SX1777_CR0,0,1777,999_AL_.jpg",
"https://images-na.ssl-images-amazon.com/images/M/MV5BMTY2ODQ3NjMyMl5BMl5BanBnXkFtZTcwODg0MTUzNA@@._V1_SX1777_CR0,0,1777,999_AL_.jpg",
"https://images-na.ssl-images-amazon.com/images/M/MV5BMTMxOTEwNDcxN15BMl5BanBnXkFtZTcwOTg0MTUzNA@@._V1_SX1777_CR0,0,1777,999_AL_.jpg",
"https://images-na.ssl-images-amazon.com/images/M/MV5BMTYxMDg1Nzk1MV5BMl5BanBnXkFtZTcwMDk0MTUzNA@@._V1_SX1500_CR0,0,1500,999_AL_.jpg"),
rating = "7.9"),
Movie(id = idList.uuidList[1],
title = "300",
year = "2006",
genre = "Action, Drama, Fantasy",
director = "Zack Snyder",
actors = "Gerard Butler, Lena Headey, Dominic West, David Wenham",
plot = "King Leonidas of Sparta and a force of 300 men fight the Persians at Thermopylae in 480 B.C.",
poster = "http://ia.media-imdb.com/images/M/MV5BMjAzNTkzNjcxNl5BMl5BanBnXkFtZTYwNDA4NjE3._V1_SX300.jpg",
images = listOf("https://images-na.ssl-images-amazon.com/images/M/MV5BMTMwNTg5MzMwMV5BMl5BanBnXkFtZTcwMzA2NTIyMw@@._V1_SX1777_CR0,0,1777,937_AL_.jpg",
"https://images-na.ssl-images-amazon.com/images/M/MV5BMTQwNTgyNTMzNF5BMl5BanBnXkFtZTcwNDA2NTIyMw@@._V1_SX1777_CR0,0,1777,935_AL_.jpg",
"https://images-na.ssl-images-amazon.com/images/M/MV5BMTc0MjQzOTEwMV5BMl5BanBnXkFtZTcwMzE2NTIyMw@@._V1_SX1777_CR0,0,1777,947_AL_.jpg"
),
rating = "7.7"),
3. navigation 이름 지정을 위한 enum class 생성(안해도 되나 확장성을 위해...)
enum class MovieScreens {
HomeScreen,
DetailScreen;
companion object {
fun fromRoute(route: String?) : MovieScreens
= when(route?.substringBefore("/")) { // "/" 까지를 잘라 내서 버림
HomeScreen.name -> HomeScreen
DetailScreen.name -> DetailScreen
null -> HomeScreen
else -> throw java.lang.IllegalArgumentException("경로($route)를 알수 없음...")
}
}
}
4. enum class를 활용해서 Navigation 구성
navController 생성(제어) → navHost 구성(콘테이너) → navGraph 구성(네비게이션 전체 지도를 갖음)
@Composable
fun MovieNavigation(){
val navController = rememberNavController() // navigation controller
NavHost(navController = navController, // navHost
startDestination = MovieScreens.HomeScreen.name
){
// nav graph 생성
composable(MovieScreens.HomeScreen.name){
HomeScreen(getMovies(), navController = navController)
}
composable(
// 외부에서 인자를 받아서 화면 구성을 위한 설정
route = MovieScreens.DetailScreen.name + "/{fromHomeScreen}",
arguments = listOf(navArgument(name = "fromHomeScreen") { type = NavType.StringType})
){
DetailsScreen(
navController = navController,
// 위에서 받은 값을 활용해서 화면 구성을 위한 인수를 전달
it.arguments?.getString("fromHomeScreen"))
// Log.d("backstack","${UUID.randomUUID().toString()}")
}
}
}
* 기본 화면은 간단하나 확장(이벤트를 받아서 화면이동을 위한 내용은 복잡함)
5. navigation에 인수를 전달하기 위한 개별 화면 composable
@Composable
fun MovieCardView(
movie: Movie = getMovies()[0],
onItemClick: (String) -> Unit = {} // 클릭 시 네비게이션에 전달할 람다
) {
var expanded by remember { mutableStateOf(false) }
Card(
modifier = Modifier
.padding(8.dp)
.fillMaxWidth()
.clickable { onItemClick("") }, // 네비게이션에 전달하는 클릭 이벤트
elevation = 6.dp,
border = BorderStroke(2.dp, color = MaterialTheme.colors.surface),
shape = RoundedCornerShape(6.dp)
) {
Row(
modifier = Modifier.padding(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Surface(
modifier = Modifier
.padding(10.dp)
.size(100.dp),
shape = CircleShape,
elevation = 9.dp
) {
val painter = rememberAsyncImagePainter(
model = ImageRequest.Builder(LocalContext.current)
.data(movie.images[0])
.crossfade(true)
.build(),
contentScale = ContentScale.Crop
)
// Image(painter = rememberImagePainter(data = movie.images[0]), contentDescription = "poster",
// contentScale = ContentScale.Crop)
} // coil
Column(
modifier = Modifier.padding(start = 4.dp),
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.Top
) {
Text(
text = "제목 : ${movie.title}",
style = TextStyle(color = Color.Blue, fontSize = 20.sp)
)
Text(
text = "출시년: ${movie.year}",
style = MaterialTheme.typography.body2.copy(color = MaterialTheme.colors.primary)
)
AnimatedVisibility (visible = expanded) {
Column(horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.Top) {
Text( buildAnnotatedString {
withStyle(style = SpanStyle(
color = Color.Blue, fontSize = 12.sp )) {
append("Plot: ")
}
withStyle(style = SpanStyle(color = Color.DarkGray,
fontSize = 10.sp,
fontWeight = FontWeight.Bold ) ){
append(movie.plot)
}
}, modifier = Modifier.padding(6.dp))
Divider(modifier = Modifier.padding(5.dp))
Text(text = "Director : ${movie.director}", style = MaterialTheme.typography.caption)
Text(text = "Actor : ${movie.actors}", style = MaterialTheme.typography.caption)
Text(text = "Rating : ${movie.rating}", style = MaterialTheme.typography.caption)
}
}
Icon(
imageVector = if(!expanded) Icons.Filled.KeyboardArrowDown else Icons.Filled.KeyboardArrowUp,
contentDescription = "down",
modifier = Modifier
.size(25.dp)
.clickable { expanded = !expanded },
tint = Color.DarkGray
)
}
}
}
}
coil을 활용하여 url(string)을 통해 이미지을 가지고 옴.
그래들 import 필요 : implementation "io.coil-kt:coil-compose:2.2.2"
coil 1.x 에서 사용하는 rememberImagePainter가 2.x 에서는 rememberAsyncImagePainter 로 변경됨
6. Lazy 화면을 구성하고, 네비게이션에 클릭 이벤트(값을 전달)를 위한 composable
@Composable
private fun MovieLazyView(movieList: List<Movie> = getMovies(), navController: NavController) {
Surface(color = MaterialTheme.colors.background) {
LazyColumn(modifier = Modifier.padding(6.dp)) {
items(items = movieList) {movie ->
MovieCardView(movie = movie) {
navController.navigate(
MovieScreens.DetailScreen.name + "/${movie.id}")
//여기서 backstackentry를 만들어서 네비게이션에 가서 관련 페이지를 찾음
}
}
}
}
}
==> 여기서 생성한 nav controller의 전달 값을 네비게이션에서 받아서 nav graph를 검색하여 해당되는 화면을 랜더링함
7. 네비게이션에서 받은 인수를 활용해서 화면을 생성하는 composable
@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
@Composable
fun DetailsScreen(navController: NavController,
fromNav: String?) {
val newMovieList = getMovies().filter {
it.id == fromNav
}
Scaffold(
topBar = {
MyTopAppBar(
navIcon = Icons.Default.ArrowBack,
desc = "Go back",
title = "Movie Detail",
navController = navController,
modifier = Modifier
.padding(vertical = 8.dp)
.clickable { navController.popBackStack() }
)
},
) {
Column() {
Surface(
modifier = Modifier
.padding(6.dp)
.fillMaxSize()
) {
Column(horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top) {
MovieCardView(movie = newMovieList.first())
Spacer(modifier = Modifier.height(8.dp))
Divider()
Text(text = newMovieList[0].id)
Text(text = newMovieList[0].genre)
Text(text = newMovieList[0].actors)
Divider()
Text("Movie Images")
RowImages(newMovieList)
}
}
}
}
}
이벤트 전달 순서(순환 구조로 좀 혼란 스러움)
5(lazy에 전달한 단일 화면 구성, 클릭 람다를 hoisting)
=> 주의 사항 : 전체 리스트에서 첫번째 것만 전달
(lazy에서는 전체 리스트 중에서 하나 씩 뽑아 내므로 결국 하나의 개별 화면 구성만 있으면 됨)
-> 6(list의 원하는 값을 전달)
-> 4(네비게이션에서 해당되는 화면을 찾아서 전달 받은 값을 다시 해당 화면에 전달)
-> 7(네비게이션에서 받은 값을 활용해서 filter를 적용해서 list에서 원하는 list를 찾아서 화면 구성에 활용)
=========================================================
네비게이션을 위한 impot 항목
implementation "androidx.navigation:navigation-compose:2.5.3"
기타 : 확장 아이콘을 위해서는 그래들 impot 필요
implementation "androidx.compose.material:material-icons-extended:1.4.0"
'Android-jetpack Compose' 카테고리의 다른 글
Android Coroutine (1) | 2023.04.02 |
---|---|
Android - Dependency Injection(Hilt)/Room 기본 개념 설정 (0) | 2023.04.01 |
jackpack compose - Stateful vs Stateless 비교 (0) | 2023.03.19 |
jetpack compose 시작하기, 관련 링크, 교육 자료 등 (0) | 2023.03.18 |
jackpack compose - Android Studio 설정 (0) | 2023.03.18 |