Group Study (2023-2024)/Android 심화

[Android 심화] 3주차 스터디 - Movie App 만들기(Scaffold, LazyColumn, Navigation)

푸리우 2023. 11. 20. 20:57

위와 같은 화면을 만들기 위해 MovieRow라는 틀을 우선 만든 다음 LazyColumn을 이용해 영화 제목이 리스트 형식으로 보이도록 만든다. 그리고 화면 상단에 TopBar를 만들어주려면 Scaffold를 이용하여 구현한다.

화면에 대한 코드는 아래와 같다.

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApp{
                MainContent()
            }
        }
    }
}

@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
@Composable
fun MyApp(content: @Composable () -> Unit){
    MovieAppTheme() {
        Scaffold(topBar={
            TopAppBar(backgroundColor = Color.Magenta,
            elevation=5.dp){
                Text(text="Movies")
            }
        }){
            content()
        }
    }
}

@Composable
fun MainContent(movieList: List<String> =listOf("Avatar","300","Harry Potter","Life","Happiness","Holiday")){
    Column(modifier=Modifier.padding(12.dp)){
        LazyColumn{
            items(items=movieList){
                MovieRow(movie = it)
            }
        }
    }
}

@Composable
fun MovieRow(movie: String){
    Card(modifier= Modifier
        .padding(4.dp)
        .fillMaxWidth()
        .height(130.dp),
        shape= RoundedCornerShape(corner= CornerSize(16.dp)),
        elevation=6.dp){
        Row(verticalAlignment=Alignment.CenterVertically,
            horizontalArrangement = Arrangement.Start){
            Surface(modifier=Modifier
                .padding(12.dp)
                .size(100.dp),
            shape=RectangleShape,
            elevation=4.dp){
                Icon(imageVector= Icons.Default.AccountBox,
                contentDescription = "Movie Image")
            }
            Text(text=movie)
        }
    }
}

이제 각각의 MovieRow를 눌렀을 때 영화 제목이 로그에 뜨도록 만들어 보겠다.

MainRow에서 Card에 clickable 코드를 작성하고 onItemClick이라는 매개변수를 추가해 준다.

fun MovieRow(movie: String, onItemClick: (String)->Unit = {}){
    Card(modifier= Modifier
        .clickable {
                   onItemClick(movie)
        },

 

MainRow에 매개변수가 추가됐으므로 MainContent에서 MovieRow에 대한 코드를 아래와 같이 수정해 준다.

MovieRow(movie = it){movie->
	Log.d("TAG", "MainContent: $movie")
}

 

이제 Navigation을 이용하여 MainRow를 눌렀을 때 다른 화면으로 넘어가고 그 화면에 영화 제목을 띄워보도록 하겠다.

Navigation은 화면 이동과 관련된 것을 다루는 Jetpack 라이브러리 중 하나이다.

그리고 크게 3개의 구성 요소로 이루어진다.

  1. Navigation Graph: Navigation과 관련해 모든 정보를 가지고 있는 구성 요소
  2. NavHost: Navigation Graph에 담겨 있는 목적지, Navigation Graph와 탐색 그래프와 NavController를 연결
  3. NavController: NavHost에 어떤 화면을 띄울 것인지 컨트롤하는 역할을 수행

 

우선 MovieNavigation 파일을 추가하여 NavController와 NavHost를 만들어준다.

컴포저블에서는 rememberNavController() 메서드를 사용하여 NavController를 만든다.

NavHost를 만들려면 이전에 rememberNavController()를 통해 만든 NavController뿐만 아니라 그래프의 시작 대상 경로도 필요하다.

@Composable
fun MovieNavigation(){
    val navController=rememberNavController()
    NavHost(navController = navController,
        startDestination = MovieScreens.HomeScreen.name){
        composable(MovieScreens.HomeScreen.name){
            HomeScreen(navController= navController)
        }
    }
}

MovieScreens 파일 추가

이때 route는 컴포저블 path를 정의하는 String이다.

enum class MovieScreens {
    HomeScreen,
    DetailsScreen;
    companion object{
        fun fromRoute(route:String?):MovieScreens
        =when(route?.substringBefore("/")){
            HomeScreen.name->HomeScreen
            DetailsScreen.name->DetailsScreen
            null->HomeScreen
            else->throw IllegalArgumentException("Route $route is not recognized")
        }
    }
}

HomeScreen 파일을 추가하고 MainActivity에 적었던 코드를 여기로 옮긴다.

@Composable
fun HomeScreen(navController: NavController){
    Scaffold(topBar={
        TopAppBar(backgroundColor = Color.Magenta,
            elevation=5.dp){
            Text(text="Movies")
        }
    }){
        MainContent(navController=navController)
    }
}

@Composable
fun MainContent(
    navController: NavController,
    movieList: List<String> =listOf("Avatar","300","Harry Potter","Life","Happiness","Holiday")){
    Column(modifier= Modifier.padding(12.dp)){
        LazyColumn{
            items(items=movieList){
                MovieRow(movie = it){movieTitle->
                    Log.d("TAG", "MainContent: $movieTitle")
                }
            }
        }
    }
}

MainActivity 코드는 아래와 같이 바뀐다.

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApp{
                MovieNavigation()
            }
        }
    }
}

@Composable
fun MyApp(content: @Composable () -> Unit){
    MovieAppTheme() {
        content()
    }
}

이제 MainRow를 눌렀을 때 아래 화면처럼 DetailScreen으로 넘어가게 만들어보겠다.

DetailScreen 파일 추가

@Composable
fun DetailsScreen(navController: NavController, movieData: String?){
    Surface(modifier= Modifier
        .fillMaxSize()){
        Column(horizontalAlignment= Alignment.CenterHorizontally,
        verticalArrangement=Arrangement.Center){
            Text(text=movieData.toString(), style=MaterialTheme.typography.h5)
        }
    }
}

MovieNavigation 파일에 아래 코드 추가

Navigation은 Composable 간의 인수 전달도 지원한다. 아래 코드에서는 movie라는 String 타입의 인수를 전달하고 있다.

그리고 인수는 NavBackStackEntry에서 추출한다.

composable(MovieScreens.DetailsScreen.name+"/{movie}",
	arguments = listOf(navArgument(name="movie"){type= NavType.StringType})) {
    backStackEntry->
    DetailsScreen(navController=navController,
        backStackEntry.arguments?.getString("movie"))
}

HomeScreen 파일에서 MainContent 코드 수정

이때 Navigate Graph에서 컴포저블 목적지로 이동하려면 navigate() 메서드를 사용한다. 아래 코드는 MovieRow를 눌렀을 때 영화 제목을 인수로 전달하고 DetailScreen으로 넘어가게 작성하였다.

MovieRow(movie = it){movie->
    navController.navigate(route=MovieScreens.DetailsScreen.name+"/$movie")
}

 

 

참고자료: 

https://developer.android.com/guide/navigation?hl=ko

https://developer.android.com/jetpack/compose/navigation?hl=ko

https://velog.io/@hoyaho/Jetpack-Navigation-Component