이동식 저장소

[Android Kotlin Fundamentals] Navigation path 본문

Primary/Android

[Android Kotlin Fundamentals] Navigation path

해스끼 2021. 2. 2. 15:40

이 글은 Google의 Android Kotlin Fundamentals를 참고하여 작성되었습니다.

 

Fragment 간의 이동은 Navigation을 이용하여 정의하는 것이 좋다. 물론 onClick 등의 콜백을 사용할 수도 있지만, Navigation을 사용하면 조건부 이동, 이전 화면으로 돌아가는 등의 액션을 더 쉽게 관리할 수 있다.

Navigation 라이브러리 사용

Android navigation library를 사용하려면 module-level build.gradle 파일에 다음을 추가해야 한다.

    implementation "androidx.navigation:navigation-fragment-ktx:2.3.3"
    implementation "androidx.navigation:navigation-ui-ktx:2.3.3"

2021.02.02. 기준 최신 버전은 2.3.3이다.

기본 개념

Navigation destination은 fragment 또는 activity 등이 될 수 있다. Navigation graph를 통해 destination간의 이동 경로를 정의할 수 있다. 보통은 하나의 activity에서 여러 개의 fragment 사이를 이동한다.

  • Navigation 타입의 리소스 파일을 만든다. 만들어진 파일은 res/navigation 폴더에 생성될 것이다. 이름은 대략 navigation.xml 정도로 짓는다.
  • navigation.xml 파일을 열어서 우측 위의 Design 탭을 클릭한다. 이곳에 destination 간의 이동 경로가 그려진다.
  • 이동 경로는 action으로 표현된다. action마다 부여된 ID를 사용하여 코드에서 어떤 action을 따라 이동할 것인지 결정할 수 있다.

Fragment가 보여질 컨테이너를 Navigation host fragment라고 한다. 보통 NavHostFragment라고 부르며 navigation graph의 호스트 역할을 한다.

  • NavHostFragment는 graph 사이를 이동할 때 fragment를 자동으로 교체하며, fragment back stack을 관리한다.
  • 예를 들어 다음의 뷰를 activity_main.xml 에서 NavHostFragment로 사용할 수 있다.
<androidx.fragment.app.FragmentContainerView
    android:id="@+id/navHostFragment"
    android:name="androidx.navigation.fragment.NavHostFragment"
    ...
/>

이동

버튼을 클릭했을 때 다른 뷰로 이동하고 싶다면 다음과 같이 하면 된다.

  • 버튼의 onClick 리스너 안에서 findNavController().navigate()를 호출한다.
  • 이때 actionID를 매개변수로 넘겨줘야 한다.

하나의 뷰에서 출발하는 여러 개의 action을 정의하고, 조건에 따라 액션을 수행할 수도 있다. 예를 들어 "다음" 버튼을 클릭하면 다음 화면으로 넘어가고, "힌트" 버튼을 클릭하면 힌트 화면으로 넘어갈 수도 있다. 어쨌든 findNavController().navigate()만 잘 기억해 놓으면 된다.

뒤로 가기?

휴대폰의 뒤로 가기 버튼을 누르면 (일반적으로) 이전에 보여진 화면으로 돌아갈 수 있다. 하지만 특정 상황에서는 메인 화면으로 바로 건너뛰어야 하는 경우도 있다.

  • navigation.xml 파일을 열어서 아무 action이나 하나 클릭해 보자. 오른쪽에 보면 Pop Behavior라는 메뉴가 있을 것이다.

    pop behavior
  • popUpTo 매개변수는 back stack에서 어느 destination까지 pop할지 를 지정하는 매개변수이다.

  • popUpToInclusive는 pop하는 과정에서 popUpTo에서 지정한 destination을 포함할지 결정하는 매개변수이다. true로 설정하면 popUpTo까지 pop되며, false로 설정하거나 비워 두면 popUpTo 바로 전까지 pop한다.

  • popUpTo를 시작 fragment로 지정하고, popUpToInclusive를 true로 지정하면 뒤로 가기 버튼을 눌렀을 때 앱이 종료된다.

또 다른 뒤로 가기

상단 바(app bar)의 왼쪽 위에는 다음과 같이 뒤로 가기 버튼을 넣을 수 있다. 영어로는 Up button이라고 부른다.

up button

Navigation controller의 Navigation UI 라이브러리를 사용하면 app bar에 up button을 추가할 수 있다.

  1. MainActivity.onCreate()에서 다음과 같이 NavigationUI.setUpActionBarWithNavController()를 실행한다. 이름이 길지만 잘 외워 보자.
val navController = this.findNavController(R.id.navHostFragment)
NavigationUI.setupActionBarWithNavController(this, navController)
  1. onSupportNavigateUp()을 다음과 같이 override한다.
override fun onSupportNavigateUp(): Boolean {
        val navController = this.findNavController(R.id.navHostFragment)
        return navController.navigateUp()
    }
}

옵션 메뉴

반대로 App bar의 오른쪽 위에는 옵션 메뉴(대략 이런 버튼

option menu

) 가 존재한다. 옵션 메뉴를 선택했을 때 특정 Fragment로 이동하도록 코딩해 보자.

  1. navigation.xml에 fragment의 ID가 정의되어 있는지 확인한다.
  2. 옵션 메뉴를 정의한다.
    • Menu 리소스 파일을 만든다. 파일 이름은 대략 options_menu.xml 정도로 하겠다. 메뉴 파일은 res/menu 폴더에 만들어진다.
    • options_menu.xml 파일을 열어서 Menu Item을 하나 추가한다.
    • 추가한 menu item의 ID를 설정한다. Fragment로 이동하는 메뉴의 경우, menu item의 ID를 navigation.xml에 정의된 Fragment의 ID와 동일하게 하자. 이러면 내부적으로 onClick 메소드가 자동으로 작성된다. 꼭 이렇게 해야 하는 것은 아니지만, 같은 기능을 더 편리하게 구현할 수 있다.
  3. App bar에 옵션 메뉴를 추가한다.
    • 옵션 메뉴가 보여질 Fragment나 Activity의 onCreateView() 안에서 setHasOptionsMenu(true)를 호출한다.
    • onCreateOptionsMenu()를 다음과 같이 override한다.
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        super.onCreateOptionsMenu(menu, inflater)
        inflater.inflate(R.menu.options_menu, menu)
}
  1. 옵션 메뉴의 클릭 이벤트를 정의한다.
    • onOptionsItemSelected()를 다음과 같이 override하여 옵션 메뉴가 클릭되었을 때 적절한 navigation action을 따라가도록 한다.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
     return NavigationUI.
            onNavDestinationSelected(item, requireView().findNavController())
            || super.onOptionsItemSelected(item)
}

Navigation drawer

요즘은 Up button이나 옵션 메뉴보다 drawer가 더 많이 보이는 것 같다. Navigation drawer는 마치 서랍처럼 가장자리에서 슥 하고 나오는 뷰를 의미한다.

navigation drawer

Drawer는 상단의 header, 하단의 menu로 구성된다. Header는 별도의 레이아웃을 지정하면 되고, menu는 메뉴 리소스 파일을 만들어야 한다.

Navigation drawer는 다음의 두 가지 방법으로 열 수 있다.

  • (보통) 왼쪽 가장자리에서 오른쪽으로 스와이프하여 연다.
  • App bar의 drawer 버튼(drawer button)을 눌러서 연다.

개발자로서 우리는 두 가지 방법을 모두 구현해야 한다. 다음과 같이 해 보자.

  1. Module-level build.gradle에 다음과 같이 의존성을 추가한다. 작성일 기준 최신 버전은 1.2.1이다.
implementation "com.google.android.material:material:1.2.1"
  1. Navigation graph의 각 Fragment에 ID를 지정한다. Drawer에 넣을 Fragment는 모두 graph에 추가되어 있어야 한다. Drawer도 Navigation으로 작동하기 때문이다.
  2. Drawer에 넣을 menu를 만들자.
    • Menu 리소스 파일을 만든다. 나는 nav_drawer_menu.xml 파일을 만들었다.
    • 만들어진 메뉴 파일에 Menu Item을 넣는다. 옵션 메뉴에서처럼 각 Item의 ID를 navigation.xml에 있는 Fragment의 ID와 동일하게 하면 onClick을 따로 만들지 않아도 된다. 메뉴를 만들 때 공통적으로 적용되는 기능이므로 잘 기억해 두자.
  3. Navigation Drawer를 추가하자.
    • Navigation host Fragment가 포함된 레이아웃을 수정해야 한다. 최상위 레이아웃을 DrawerLayout으로 감싸주자. 정확한 이름은 androidx.drawerlayout.widget.DrawerLayout이다.
    • DrawerLayout 바로 밑에 NavigationView를 추가한다. 정확한 이름은 com.google.android.material.navigation.NavigationView이다.
  4. Drawer에 navigation controller를 연결한다.
    • Navigation controller를 생성한 Activity로 가서, 다음과 같이 onCreate() 안에 NavigationUI.setUpWithNavController()를 호출한다.
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(
       this, R.layout.activity_main)
NavigationUI.setupWithNavController(binding.navView, navController)
  1. App bar에 drawer button을 추가한다.
    • onCreate()에서 다음과 같이 NavigationUI.setupActionBarWithNavController()를 호출한다.
NavigationUI.setupActionBarWithNavController(
    this, navController, binding.drawerLayout)
  1. 사실 drawer 버튼의 자리는 위에서 추가했던 up button의 자리와 동일하다. onSupportNavigateUp()을 다음과 같이 수정하여 두 버튼이 자연스럽게 동작하도록 한다.
override fun onSupportNavigateUp(): Boolean {
   val navController = this.findNavController(R.id.navHostFragment)
   return NavigationUI.navigateUp(navController, binding.drawerLayout)
}
Comments