HOME > android > jetpack

Android Jetpack - Navigation 사용 방법, 튜토리얼로 알아보기

JSFollow19 Nov 2018

AndroidX의 안드로이드 네비게이션(Navigation)은 UI 전환을 쉽게 구현하는데 도와주는 라이브러리입니다. 개발자가 정의한 UI Graph를 기반으로 화면을 쉽게 전환하고, 유지보수가 매우 쉬워집니다.

이 글에서는 네비게이션(Navigation)을 사용한 간단한 앱을 만들어 봄으로 써, Navigation의 기본적인 사용 방법을 설명합니다.

프로젝트 생성

Empty Activity로 생성합니다. androidx navigation

KotlinUse AndroidX artifacts를 선택해주세요.(Android Studio 버전이 3.4 미만이라면 이 기능이 없습니다. 마이그레이션 기능을 이용하거나 직접 의존성을 변경해줘야 합니다) androidx navigation

AndroidX로 프로젝트를 생성하지 않았다면, 메뉴에서 [Refactor] -> [Migrate to AndroidX...] 를 누르시면 AndroidX를 사용하는 프로젝트로 마이그레이션이 됩니다.

네비게이션을 사용하려면 앱 gradle에서 dependency를 추가해야 합니다.

dependencies {
  ....
  implementation 'android.arch.navigation:navigation-fragment:1.0.0-alpha07'
  implementation 'android.arch.navigation:navigation-ui-ktx:1.0.0-alpha07'
}

Fragment 생성

앞으로 3개의 화면을 순환하는 앱을 만들어볼 것인데요. 3개의 화면을 각각 London, Paris, Italy라고 할께요. 3개의 화면은 Fragment로 구현할 것이고 각각의 파일에 class를 만들겠습니다. 아래 코드를 참고하여 클래스와 레이아웃 파일을 모두 구현해줍니다.

LondonScreen.kt
class ItalyScreen : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_italy_screen, container, false)

        return view
    }
}
ParisScreen.kt
class ParisScreen : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_paris_screen, container, false)

        return view
    }
}
ItalyScreen.kt
class ItalyScreen : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_italy_screen, container, false)

        return view
    }
}
/res/layout/fragment_italy_screen.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ItalyScreen">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Italy"
        android:textSize="30sp"
        android:textStyle="bold"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintBottom_toTopOf="@+id/to_london_from_italy"/>

    <Button
        android:id="@+id/to_london_from_italy"
        style="@style/Widget.AppCompat.Button.Borderless"
        android:layout_width="wrap_content"
        android:layout_height="48dp"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp"
        android:text="Go to London"
        android:textColor="@color/colorAccent"
        android:textSize="18sp"
        android:textStyle="bold"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"/>

    <Button
        android:id="@+id/to_paris_from_italy"
        style="@style/Widget.AppCompat.Button.Borderless"
        android:layout_width="wrap_content"
        android:layout_height="48dp"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp"
        android:text="Go to Paris"
        android:textColor="@color/colorAccent"
        android:textSize="18sp"
        android:textStyle="bold"
        android:layout_marginTop="30dp"
        app:layout_constraintTop_toBottomOf="@+id/to_london_from_italy"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>
/res/layout/fragment_london_screen.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".LondonScreen">

    <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="London"
            android:textSize="30sp"
            android:textStyle="bold"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintBottom_toTopOf="@+id/to_paris_from_london"/>

    <Button
            android:id="@+id/to_paris_from_london"
            style="@style/Widget.AppCompat.Button.Borderless"
            android:layout_width="wrap_content"
            android:layout_height="48dp"
            android:layout_marginStart="8dp"
            android:layout_marginEnd="8dp"
            android:text="Go to Paris"
            android:textColor="@color/colorAccent"
            android:textSize="18sp"
            android:textStyle="bold"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"/>

    <Button
            android:id="@+id/to_italy_from_london"
            style="@style/Widget.AppCompat.Button.Borderless"
            android:layout_width="wrap_content"
            android:layout_height="48dp"
            android:layout_marginStart="8dp"
            android:layout_marginEnd="8dp"
            android:text="Go to Italy"
            android:textColor="@color/colorAccent"
            android:textSize="18sp"
            android:textStyle="bold"
            android:layout_marginTop="30dp"
            app:layout_constraintTop_toBottomOf="@+id/to_paris_from_london"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>
/res/layout/fragment_paris_screen.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".ParisScreen">

    <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Paris"
            android:textSize="30sp"
            android:textStyle="bold"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintBottom_toTopOf="@+id/to_italy_from_paris"/>

    <Button
        android:id="@+id/to_italy_from_paris"
        style="@style/Widget.AppCompat.Button.Borderless"
        android:layout_width="wrap_content"
        android:layout_height="48dp"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp"
        android:text="Go to Italy"
        android:textColor="@color/colorAccent"
        android:textSize="18sp"
        android:textStyle="bold"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"/>

    <Button
        android:id="@+id/to_london_from_paris"
        style="@style/Widget.AppCompat.Button.Borderless"
        android:layout_width="wrap_content"
        android:layout_height="48dp"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp"
        android:text="Go to London"
        android:textColor="@color/colorAccent"
        android:textSize="18sp"
        android:textStyle="bold"
        android:layout_marginTop="30dp"
        app:layout_constraintTop_toBottomOf="@+id/to_italy_from_paris"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Navigation Host 정의

Navigation을 구현할 때 Host를 정의해야 합니다. Host는 화면이 보이는 객체입니다. 여기서는 MainActivity를 Host로 설정할 것입니다.

MainActivity.ktactivity_main.xml를 구현해주세요.

MainActivity.kt
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

    }
}
/res/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/background_light"
        tools:context=".MainActivity">

    <fragment
            android:id="@+id/my_nav_host_fragment"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:defaultNavHost="true"
            app:navGraph="@navigation/nav_graph"/>
</FrameLayout>

activity_main.xmlFrameLayout안에 frament view가 존재하도록 구현되어있습니다. defaultNavHostnavGraphfragment를 Host로 설정하는 속성입니다. 코틀린 파일에서 Host를 설정하지 않고 레이아웃 파일에서만 그래프와 호스트를 설정할 수 있습니다.

  • defaultNavHost를 true로 설정하면 Back key를 눌렀을 때 이전 화면으로 전환됩니다. 만약 false로 설정하고 Back key를 누르면 앱을 종료합니다.
  • navGraph는 graph를 정의한 파일을 의미합니다.

Graph 정의

예를들어 A라는 화면에서 B화면으로 이동할 수 있다면, 그 관계를 그래프에 정의해야 합니다. /res/navigation/폴더를 생성하고 그 폴더에 nav_graph.xml을 생성해주세요. 이 파일이 View의 navigation 관계가 정의된 그래프입니다.

/res/navigation/nav_graph.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            xmlns:tools="http://schemas.android.com/tools"
            app:startDestination="@+id/london_screen">

    <fragment
            android:id="@+id/london_screen"
            android:name="com.codechacha.navigation.LondonScreen"
            android:label="London screen"
            tools:layout="@layout/fragment_london_screen">
    </fragment>

    <fragment
            android:id="@+id/paris_screen"
            android:name="com.codechacha.navigation.ParisScreen"
            android:label="Paris screen"
            tools:layout="@layout/fragment_paris_screen">
    </fragment>

    <fragment
            android:id="@+id/italy_screen"
            android:name="com.codechacha.navigation.ItalyScreen"
            android:label="Italy screen"
            tools:layout="@layout/fragment_italy_screen">
    </fragment>

</navigation>

코드를 보면 navigation 태그 아래에 fragment 태그가 3개 있습니다. fragment는 우리가 순환할 화면입니다. name을 보면 위에서 생성한 LondonScreen, ParisScreen, ItalyScreen이 설정되어있습니다.

navigation 태그에 startDestination 속성이 있는데 처음 보여질 화면이 어떤 것인지를 의미합니다. 여기서는 london_screen으로 설정되어있기 때문에 처음 보이는 화면이 LondonScreen이 됩니다.

이제 실행해보면 LondonScreen이 보입니다.

android jetpack navigation

시작 화면은 잘 보이지만 GO TO PARIS버튼을 눌러도 화면이 이동하지 않습니다. 그래프에서 화면 이동 관계를 설정하지 않았기 때문입니다. nav_graph.xml에서 왼쪽 하단의 Design버튼을 누르면 UI로 쉽게 이동 관계를 설정할 수 있습니다.

android jetpack navigation

다시 Text버튼을 눌러 코드를 확인해보면... fragment 태그 아래에 action 코드들이 자동 삽입된 것을 볼 수 있습니다. action에는 id와 이동하려는 화면을 의미하는 desination이 정의되어 있습니다.

/res/navigation/nav_graph.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            xmlns:tools="http://schemas.android.com/tools"
            app:startDestination="@+id/london_screen">

    <fragment
            android:id="@+id/london_screen"
            android:name="com.codechacha.navigation.LondonScreen"
            android:label="London screen"
            tools:layout="@layout/fragment_london_screen">
        <action android:id="@+id/action_london_screen_to_paris_screen"
                app:destination="@id/paris_screen"/>
        <action android:id="@+id/action_london_screen_to_italy_screen"
                app:destination="@id/italy_screen"/>
    </fragment>

    <fragment
            android:id="@+id/paris_screen"
            android:name="com.codechacha.navigation.ParisScreen"
            android:label="Paris screen"
            tools:layout="@layout/fragment_paris_screen">
        <action android:id="@+id/action_paris_screen_to_italy_screen"
                app:destination="@id/italy_screen"/>
        <action android:id="@+id/action_paris_screen_to_london_screen"
                app:destination="@id/london_screen"/>
    </fragment>

    <fragment
            android:id="@+id/italy_screen"
            android:name="com.codechacha.navigation.ItalyScreen"
            android:label="Italy screen"
            tools:layout="@layout/fragment_italy_screen">
        <action android:id="@+id/action_italy_screen_to_paris_screen"
                app:destination="@id/paris_screen"/>
        <action android:id="@+id/action_italy_screen_to_london_screen"
                app:destination="@id/london_screen"/>
    </fragment>

</navigation>

이제 fragment에서 버튼을 눌렀을 때 이동하는 코드를 작성하면 됩니다. 버튼을 눌렀을 때 화면을 변경하고 싶으면 Navigation Controller 객체를 이용해야 합니다. Controller는 Host에 보여지고 있는 View를 변경해주는 역할을 맡고 있습니다.

Fragment 파일들을 열고 버튼 리스너를 정의해주세요. Navigation.findNavController는 Controller를 가져오는 코드이고, navigate는 정의된 action의 destination으로 이동하라는 의미입니다.

LondonScreen.kt
class LondonScreen : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_london_screen, container, false)

        view.findViewById<Button>(R.id.to_paris_from_london).setOnClickListener {
            Navigation.findNavController(view).navigate(R.id.action_london_screen_to_paris_screen)
        }
        view.findViewById<Button>(R.id.to_italy_from_london).setOnClickListener {
            Navigation.findNavController(view).navigate(R.id.action_london_screen_to_italy_screen)
        }

        return view
    }
}
ItalyScreen.kt
class ItalyScreen : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_italy_screen, container, false)

        view.findViewById<Button>(R.id.to_london_from_italy).setOnClickListener {
            Navigation.findNavController(view).navigate(R.id.action_italy_screen_to_london_screen)
        }
        view.findViewById<Button>(R.id.to_paris_from_italy).setOnClickListener {
            Navigation.findNavController(view).navigate(R.id.action_italy_screen_to_paris_screen)
        }

        return view
    }
}
ParisScreen.kt
class ParisScreen : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_paris_screen, container, false)

        view.findViewById<Button>(R.id.to_italy_from_paris).setOnClickListener {
            Navigation.findNavController(view).navigate(R.id.action_paris_screen_to_italy_screen)
        }
        view.findViewById<Button>(R.id.to_london_from_paris).setOnClickListener {
            Navigation.findNavController(view).navigate(R.id.action_paris_screen_to_london_screen)
        }

        return view
    }
}

이제 실행해보세요. 버튼을 누르면 우리가 의도한 대로 화면이 이동합니다. 그리고 Back key를 누르면 이전 화면으로 이동됩니다. 처음 화면에서 Back key를 누르면 앱이 종료됩니다.

Animation

지금까지 구현한 앱은 화면이 변경될 때 애니메이션 없이 바로 전환이 되는데요. action마다 애니메이션을 정의할 수 있습니다. 애니메이션 정의는 navigation이 정의된 그래프 파일에서 할 수 있지만, 그 전에 애니메이션들을 구현해야 합니다. /res/anim/폴더를 만들고 4개의 애니메이션 파일을 구현해주세요.

android jetpack navigation

/res/anim/slide_in_left.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">

    <translate android:fromXDelta="-100%" android:toXDelta="0%"
               android:fromYDelta="0%" android:toYDelta="0%"
               android:duration="700"/>
</set>
/res/anim/slide_in_right.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">

    <translate android:fromXDelta="100%" android:toXDelta="0%"
               android:fromYDelta="0%" android:toYDelta="0%"
               android:duration="700"/>
</set>
/res/anim/slide_out_left.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">

    <translate android:fromXDelta="0%" android:toXDelta="-100%"
               android:fromYDelta="0%" android:toYDelta="0%"
               android:duration="700"/>
</set>

/res/anim/slide_out_right.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">

    <translate android:fromXDelta="0%" android:toXDelta="100%"
               android:fromYDelta="0%" android:toYDelta="0%"
               android:duration="700"/>
</set>

애니메이션을 파일로 정의하였으면 그래프 파일에 설정하면 됩니다. nav_graph.xml에 각각의 Action에 애니메이션을 설정합니다.

/res/navigation/nav_graph.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            xmlns:tools="http://schemas.android.com/tools"
            app:startDestination="@+id/london_screen">

    <fragment
            android:id="@+id/london_screen"
            android:name="com.codechacha.navigation.LondonScreen"
            android:label="London screen"
            tools:layout="@layout/fragment_london_screen"
            app:popEnterAnim="@anim/slide_in_left"
            app:popExitAnim="@anim/slide_out_right"
            app:enterAnim="@anim/slide_in_right"
            app:exitAnim="@anim/slide_out_left">
        <action android:id="@+id/action_london_screen_to_paris_screen"
                app:destination="@id/paris_screen"
                app:popEnterAnim="@anim/slide_in_left"
                app:popExitAnim="@anim/slide_out_right"
                app:enterAnim="@anim/slide_in_right"
                app:exitAnim="@anim/slide_out_left"/>
        <action android:id="@+id/action_london_screen_to_italy_screen"
                app:destination="@id/italy_screen"
                app:popEnterAnim="@anim/slide_in_left"
                app:popExitAnim="@anim/slide_out_right"
                app:enterAnim="@anim/slide_in_right"
                app:exitAnim="@anim/slide_out_left"/>
    </fragment>

    <fragment
            android:id="@+id/paris_screen"
            android:name="com.codechacha.navigation.ParisScreen"
            android:label="Paris screen"
            tools:layout="@layout/fragment_paris_screen">
        <action android:id="@+id/action_paris_screen_to_italy_screen"
                app:destination="@id/italy_screen"
                app:popEnterAnim="@anim/slide_in_left"
                app:popExitAnim="@anim/slide_out_right"
                app:enterAnim="@anim/slide_in_right"
                app:exitAnim="@anim/slide_out_left"/>
        <action android:id="@+id/action_paris_screen_to_london_screen"
                app:destination="@id/london_screen"
                app:popEnterAnim="@anim/slide_in_left"
                app:popExitAnim="@anim/slide_out_right"
                app:enterAnim="@anim/slide_in_right"
                app:exitAnim="@anim/slide_out_left"/>
    </fragment>

    <fragment
            android:id="@+id/italy_screen"
            android:name="com.codechacha.navigation.ItalyScreen"
            android:label="Italy screen"
            tools:layout="@layout/fragment_italy_screen">
        <action android:id="@+id/action_italy_screen_to_paris_screen"
                app:destination="@id/paris_screen"
                app:popEnterAnim="@anim/slide_in_left"
                app:popExitAnim="@anim/slide_out_right"
                app:enterAnim="@anim/slide_in_right"
                app:exitAnim="@anim/slide_out_left"/>
        <action android:id="@+id/action_italy_screen_to_london_screen"
                app:destination="@id/london_screen"
                app:popEnterAnim="@anim/slide_in_left"
                app:popExitAnim="@anim/slide_out_right"
                app:enterAnim="@anim/slide_in_right"
                app:exitAnim="@anim/slide_out_left"/>
    </fragment>

</navigation>
  • enterAnim : action을 수행할 때 이동할 화면(destination)에 대한 애니메이션입니다.
  • exitAnim : action을 수행할 때 현재 화면에 대한 애니메이션입니다.
  • popExitAnim : 지난 화면으로 돌아갈 때(pop) 종료되는 화면에 대한 애니메이션입니다.
  • popEnterAnim : 지난 화면으로 돌아갈 때 이동할 화면에 대한 애니메이션입니다.

앱을 실행하고 버튼을 누르면 애니메이션이 적용된 것을 확인할 수 있습니다.

Global Action

사실 nav_graph.xml에는 중복된 Action들이 3개나 있습니다. 컨트롤 CV로 일단 구현을 해놨는데요, 구현이 끝났으니 코드를 깔끔하게 정리해보려합니다. Global action은 말 그대로 Global로 action을 정의해서 여러 객체에서 공통으로 사용하자는 것입니다.

Global action을 생성하는 방법은 코드로 할 수 있고, 그래프 에디터로도 할 수 있습니다. nav_graph.xml에서 왼쪽 하단에 Design버튼을 누르면 그래프 에디터가 실행됩니다. View를 클릭한 상태에서 우클릭 -> Add Action -> Global을 누르면 Global action이 생성됩니다.

3개의 fragment의 Global action을 모두 생성하고 xml을 열어 보면 아래처럼 navigation 태그 하위에 Global action 코드가 생성됩니다.

<action android:id="@+id/action_global_london_screen"
        app:destination="@id/london_screen"/>

<action android:id="@+id/action_global_paris_screen"
        app:destination="@id/paris_screen"/>

<action android:id="@+id/action_global_italy_screen"
        app:destination="@id/italy_screen"/>

기존에 생성한 Fragment 내의 Action을 모두 삭제하고, Global action에 애니메이션을 추가하면 다음과 같은 코드가 됩니다.

/res/navigation/nav_graph.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            xmlns:tools="http://schemas.android.com/tools"
            app:startDestination="@+id/london_screen">

    <fragment
            android:id="@+id/london_screen"
            android:name="com.codechacha.navigation.LondonScreen"
            android:label="London screen"
            tools:layout="@layout/fragment_london_screen">
    </fragment>

    <fragment
            android:id="@+id/paris_screen"
            android:name="com.codechacha.navigation.ParisScreen"
            android:label="Paris screen"
            tools:layout="@layout/fragment_paris_screen">
    </fragment>

    <fragment
            android:id="@+id/italy_screen"
            android:name="com.codechacha.navigation.ItalyScreen"
            android:label="Italy screen"
            tools:layout="@layout/fragment_italy_screen">
    </fragment>

    <action android:id="@+id/action_global_london_screen"
            app:destination="@id/london_screen"
            app:popEnterAnim="@anim/slide_in_left"
            app:popExitAnim="@anim/slide_out_right"
            app:enterAnim="@anim/slide_in_right"
            app:exitAnim="@anim/slide_out_left"/>

    <action android:id="@+id/action_global_paris_screen"
            app:destination="@id/paris_screen"
            app:popEnterAnim="@anim/slide_in_left"
            app:popExitAnim="@anim/slide_out_right"
            app:enterAnim="@anim/slide_in_right"
            app:exitAnim="@anim/slide_out_left"/>

    <action android:id="@+id/action_global_italy_screen"
            app:destination="@id/italy_screen"
            app:popEnterAnim="@anim/slide_in_left"
            app:popExitAnim="@anim/slide_out_right"
            app:enterAnim="@anim/slide_in_right"
            app:exitAnim="@anim/slide_out_left"/>

</navigation>

Local action을 모두 삭제하고, Global action을 생성했기 때문에 Fragment 코드도 변경해줘야 합니다. 아래 코드처럼 Fragment에서 사용한 Local action을 모두 Global action으로 변경해주세요.

view.findViewById<Button>(R.id.to_london_from_italy).setOnClickListener {
    Navigation.findNavController(view).navigate(R.id.action_global_london_screen)
}
view.findViewById<Button>(R.id.to_paris_from_italy).setOnClickListener {
    Navigation.findNavController(view).navigate(R.id.action_global_paris_screen)
}

이제 앱을 실행해보면 이전과 동일하게 동작하는 화면을 볼 수 있습니다.

AppBarConfiguration

현재 보여지고 있는 화면의 Label을 AppBar에 보여주고 싶을 수 있습니다. AppBarConfiguration을 이용하면 네비게이션 그래프에 정의된 Label을 AppBar에 출력할 수 있습니다. 더불어 AppBar에 뒤로가기(<-) 버튼까지 보여줍니다. 뒤로가기 버튼은 startDestination로 정의된 처음 화면이 아닐 때만 보여줍니다.

NavControllerAppBarConfiguration를 멤버변수로 생성해야 합니다. 멤버변수로 선언하는 이유는 다른 곳에서 사용하기 때문입니다. NavController는 호스트 리소스를 인자로 넘겨주어 생성합니다. AppBarConfiguration는 Controller의 graph 변수를 인자로 넘겨주어 생성합니다.

navController = Navigation.findNavController(this, R.id.my_nav_host_fragment)
appBarConfiguration = AppBarConfiguration(navController.graph)

setupActionBarWithNavController는 두 객체를 AppBar에 적용합니다.

setupActionBarWithNavController(navController, appBarConfiguration)

AppBar에 생성되는 뒤로가기 버튼을 눌렀을 때, 뒤로 이동하려면 onSupportNavigateUp를 오버라이드해야 합니다.

override fun onSupportNavigateUp(): Boolean {
    return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}

전체적인 코드는 이렇게 됩니다.

MainActivity.kt
class MainActivity : AppCompatActivity() {

    private lateinit var appBarConfiguration: AppBarConfiguration
    private lateinit var navController: NavController

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        navController = Navigation.findNavController(this, R.id.my_nav_host_fragment)
        appBarConfiguration = AppBarConfiguration(navController.graph)
        setupActionBarWithNavController(navController, appBarConfiguration)
    }

    override fun onSupportNavigateUp(): Boolean {
        return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
    }
}

실행해보면 현재 화면의 Label과 뒤로가기 버튼이 생성됩니다. 첫 화면으로 지정된 화면 일 때는 뒤로가기 버튼이 보이지 않습니다. 지금 예제는 무한으로 순환하기 때문에 조금 어색할 수 있겠네요. 실제로 순환하는 그래프를 만들어서는 안된다고 기억하시면 될 것 같네요.

android jetpack 네비게이션

다음 글 Android Jetpack Navigation을 이용하여 Drawer 구현에서는 Navigation을 이용하여 Drawer를 구현하는 방법에 대해서 알아보겠습니다.

참고