Jetpack Navigation

탐색 아키텍처 구성요소는 탐색 구현을 간소화하면서 개발자가 앱의 탐색 흐름을 시각화하는 데 도움을 줍니다. 이 라이브러리에서 제공하는 여러 이점에는 다음이 포함됩니다.

  • 프래그먼트 트랜잭션 자동 처리
  • 기본적으로 위로뒤로 올바르게 처리
  • 애니메이션 및 전환의 기본 동작
  • 일급 작업으로서의 딥 링크
  • 추가 작업 없이 탐색 UI 패턴(예: 탐색 창과 하단 탐색 메뉴) 구현
  • 탐색 중에 정보를 전달할 때 유형 안전성
  • 앱의 탐색 흐름을 시각화하고 수정하는 Android 스튜디오 도구

실행할 작업

이 Codelab에서는 아래에 표시된 샘플 앱으로 작업합니다.

모든 활동과 프래그먼트는 이미 만들어져 있으므로 탐색 구성요소를 사용하여 연결하고 그 과정에서 다음을 구현합니다.

  • 시각적 탐색 그래프
  • 대상과 작업으로 탐색
  • 전환 애니메이션
  • 메뉴 탐색, 하단 탐색 메뉴, 메뉴 창 탐색
  • 유형 안전 인수 전달
  • 딥 링크

기본 요건

  • 기본적인 Kotlin 지식(이 Codelab은 Kotlin을 사용함)
  • Android 스튜디오 3.2 이상
  • API 14 이상을 실행하는 에뮬레이터나 기기

코드 가져오기

GitHub에서 탐색 Codelab을 클론합니다.

$ git clone https://github.com/googlecodelabs/android-navigation

또는 저장소를 ZIP 파일로 다운로드할 수 있습니다.

ZIP 파일 다운로드

Android 스튜디오 3.3 이상 다운로드

Android 스튜디오 3.3 이상을 사용하는지 확인합니다. Android 스튜디오 탐색 도구에 필요합니다.

최신 버전의 Android 스튜디오를 다운로드해야 할 경우 여기에서 다운로드하면 됩니다.

탐색 개요

탐색 구성요소는 조화롭게 작동하는 세 가지 주요 부분으로 구성됩니다. 주요 부분은 다음과 같습니다.

  1. 탐색 그래프(새 XML 리소스) - 하나의 중앙 위치에 모든 탐색 관련 정보가 포함된 리소스입니다. 여기에는 대상이라고 하는 앱의 모든 위치와 사용자가 앱에서 선택할 수 있는 가능한 경로가 포함됩니다.
  2. NavHostFragment(레이아웃 XML 뷰) - 레이아웃에 추가하는 특수 위젯입니다. 탐색 그래프와는 다른 대상을 표시합니다.
  3. NavController(Kotlin/자바 객체) - 탐색 그래프 내에서 현재 위치를 계속 추적하는 객체입니다. 탐색 그래프에서 이동할 때 NavHostFragment에서 대상 콘텐츠 전환을 조정합니다.

탐색 시 NavController 객체를 사용하여 이동하려는 위치나 탐색 그래프에서 선택하려는 경로를 알려줍니다. 그러면 NavController가 NavHostFragment에 적절한 대상을 표시합니다.

지금까지는 기본 개념이었습니다. 이제 실제로 어떻게 표시되는지 보겠습니다. 먼저 새 탐색 그래프 리소스로 시작합니다.

대상

탐색 구성요소에는 대상이라는 개념이 도입됩니다. 대상은 앱에서 이동할 수 있는 모든 위치이며 일반적으로 프래그먼트나 활동입니다. 이러한 대상은 기본적으로 지원되지만 필요한 경우 자체 맞춤 대상 유형을 생성해도 됩니다.

탐색 그래프는 사용자가 앱에서 선택할 수 있는 가능한 모든 경로를 정의하는 새로운 리소스 유형입니다. 지정된 대상에서 도달할 수 있는 모든 대상을 시각적으로 표시합니다. Android 스튜디오에서는 Navigation Editor에 그래프를 표시합니다. 다음은 앱에서 만드는 시작 탐색 그래프의 일부입니다.

홈, 1단계, 2단계

Navigation Editor 탐색

1. res/navigation/mobile_navigation.xml을 엽니다.

2. Design을 클릭하여 디자인 모드로 이동합니다.

다음과 같이 표시됩니다.

탐색 그래프에는 사용 가능한 대상이 표시됩니다. 대상 사이의 화살표를 작업이라고 합니다. 작업에 관한 자세한 내용은 나중에 확인합니다.

3. 대상을 클릭하여 속성을 확인합니다.

4. 화살표로 표시된 작업을 클릭하여 속성을 확인합니다.

탐색 XML 파일 분석

그래픽 Navigation Editor에서 변경한 모든 사항은 Layout Editor가 레이아웃 XML을 수정하는 방식과 비슷하게 기본 XML 파일을 변경합니다.

Text 탭을 클릭합니다.

XML이 다음과 같이 표시됩니다.

<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/home_dest">

    <!-- ...tags for fragments and activities here -->

</navigation>

참고:

  • <navigation>은 모든 탐색 그래프의 루트 노드입니다.
  • <navigation>에는 <activity> 또는 <fragment> 요소로 표시된 대상이 하나 이상 포함됩니다.
  • app:startDestination은 사용자가 앱을 처음 열 때 기본적으로 실행되는 대상을 지정하는 속성입니다.

프래그먼트 대상을 살펴보겠습니다.

<fragment
    android:id="@+id/flow_step_one_dest"
    android:name="com.example.android.codelabs.navigation.FlowStepFragment"
    tools:layout="@layout/flow_step_one_fragment">
    <argument
        .../>

    <action
        android:id="@+id/next_action"
        app:destination="@+id/flow_step_two_dest">
    </action>
</fragment>

참고:

  • android:id는 이 XML 및 코드의 다른 위치에서 대상을 참조하는 데 사용할 수 있는 프래그먼트의 ID를 정의합니다.
  • android:name은 대상으로 이동할 때 인스턴스화할 프래그먼트의 정규화된 클래스 이름을 선언합니다.
  • tools:layout은 그래픽 편집기에서 표시해야 하는 레이아웃을 지정합니다.

일부 <fragment> 태그에도 <action>, <argument>,, <deepLink>가 포함되며 모두 나중에 다룹니다.

샘플 앱은 그래프의 대상 몇 개로 시작됩니다. 이 단계에서는 완전히 새로운 대상을 추가합니다. 탐색 그래프에 대상을 추가해야 대상으로 이동할 수 있습니다.

1. res/navigation/mobile_navigation.xml을 열고 Design 탭을 클릭합니다.

2. New Destination 아이콘을 클릭하고 'settings_fragment'를 선택합니다.

그러면 디자인 보기에서 프래그먼트 레이아웃의 미리보기를 렌더링하는 새 대상이 생깁니다.

XML 파일을 직접 수정하여 대상을 추가할 수도 있습니다.

mobile_navigation.xml

<fragment
    android:id="@+id/settings_dest"
    android:name="com.example.android.codelabs.navigation.SettingsFragment"
    android:label="@string/settings"
    tools:layout="@layout/settings_fragment" />

이제 훌륭한 탐색 그래프가 있지만 실제로 탐색에 사용하고 있지는 않습니다.

활동과 탐색

탐색 구성요소는 탐색 원리에 설명된 안내를 따릅니다. 탐색 원리에서는 활동을 앱의 시작 지점으로 사용할 것을 권장합니다. 활동에는 하단 탐색 메뉴와 같은 전역 탐색도 포함됩니다.

이에 비해 프래그먼트는 실제 대상별 레이아웃이 됩니다.

모두가 작동하도록 하려면 활동 레이아웃을 수정하여 NavHostFragment라는 특수 위젯을 포함해야 합니다. NavHostFragment는 탐색 그래프를 탐색할 때 여러 프래그먼트 대상을 안팎으로 교체합니다.

위 그림과 유사한 탐색을 지원하는 간단한 레이아웃은 다음과 같습니다. 이 코드의 예는 res/layout-470dp/navigation_activity.xml에서 확인할 수 있습니다.

<LinearLayout
    .../>
    <androidx.appcompat.widget.Toolbar
        .../>
    <fragment
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:id="@+id/my_nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:navGraph="@navigation/mobile_navigation"
        app:defaultNavHost="true"
        />
    <com.google.android.material.bottomnavigation.BottomNavigationView
        .../>
</LinearLayout>

참고:

  • 활동의 레이아웃입니다. 하단 탐색 메뉴와 툴바 등의 전역 탐색이 포함되어 있습니다.
  • android:name="androidx.navigation.fragment.NavHostFragment"app:defaultNavHost="true"는 시스템 뒤로 버튼을 NavHostFragment에 연결합니다.
  • app:navGraph="@navigation/mobile_navigation"NavHostFragment를 탐색 그래프와 연결합니다. 이 탐색 그래프는 사용자가 이동할 수 있는 대상을 모두 NavHostFragment에서 지정합니다.

마지막으로 사용자가 버튼을 클릭하는 등의 작업을 할 때 탐색 명령어를 트리거해야 합니다. NavController라는 특수 클래스는 NavHostFragment에서 프래그먼트 교체를 트리거하는 것입니다.

// Command to navigate to flow_step_one_dest
findNavController().navigate(R.id.flow_step_one_dest)

이동하려면 대상이나 작업 ID를 전달합니다. 다음은 탐색 그래프 XML에 정의된 ID입니다. 다음은 대상 ID를 전달하는 예입니다.

NavController는 강력합니다. navigate() 또는 popBackStack(),과 같은 메서드를 호출할 때 이동하는 대상의 유형에 따라 이러한 명령어를 적절한 프레임워크 작업으로 변환하기 때문입니다. 예를 들어 활동 대상과 함께 navigate()를 호출하면 NavController에서 개발자를 대신해 startActivity()를 호출합니다.

NavHostFragment와 연결된 NavController 객체를 가져오는 방법은 몇 가지가 있습니다. Kotlin에서는 탐색 명령어를 프래그먼트 내에서 호출하는지 활동에서 호출하는지 뷰에서 호출하는지에 따라 다음 확장 함수 중 하나를 사용하는 것이 좋습니다.

이제 NavController를 사용하여 이동할 차례입니다. Navigate To Destination 버튼을 연결하여 flow_step_one_dest 대상(FlowStepFragment인 대상)으로 이동합니다.

1. HomeFragment.kt를 엽니다.

2. onViewCreated()에서 navigate_destination_button을 연결합니다.

HomeFragment.kt

val button = view.findViewById<Button>(R.id.navigate_destination_button)
button?.setOnClickListener {
    findNavController().navigate(R.id.flow_step_one_dest, null)
}

3. 앱을 실행하고 Navigate to Destination 버튼을 클릭합니다. 버튼은 flow_step_one_dest 대상으로 이동합니다.

클릭 리스너 코드는 다음과 같습니다.

val button = view.findViewById<Button>(R.id.navigate_destination_button)
button?.setOnClickListener(
        Navigation.createNavigateOnClickListener(R.id.flow_step_one_dest, null)
)

navigate() 호출에는 아래와 같이 흥미롭지 않은 기본 전환이 연결되어 있습니다.

홈 사용자가 Navigate to Destination을 클릭하여 1단계로 이동합니다.

호출과 연결된 다른 속성과 함께 기본 전환은 일련의 NavOptions를 포함하여 재정의할 수 있습니다. NavOptionsBuilder 패턴을 사용하므로 필요한 옵션만 재정의하고 설정할 수 있습니다. NavOptions용 ktx DSL도 사용할 수 있습니다.

애니메이션 전환의 경우 anim 리소스 폴더에서 XML 애니메이션 리소스를 정의하고 이러한 애니메이션을 전환에 사용할 수 있습니다. 앱 코드에 포함된 예는 다음과 같습니다.

맞춤 전환 추가

Navigate to Destination 버튼을 누르면 맞춤 전환 애니메이션이 표시되도록 코드를 업데이트합니다.

1. HomeFragment.kt를 엽니다.

2. NavOptions를 정의하여 navigate_destination_buttonnavigate() 호출에 전달합니다.

val options = navOptions {
    anim {
        enter = R.anim.slide_in_right
        exit = R.anim.slide_out_left
        popEnter = R.anim.slide_in_left
        popExit = R.anim.slide_out_right
    }
}
view.findViewById<Button>(R.id.navigate_destination_button)?.setOnClickListener {
    findNavController().navigate(R.id.flow_step_one_dest, null, options)
}

3. 5단계에서 추가한 코드가 아직 있는 경우 삭제합니다.

4. Navigate to Destination 버튼을 탭하면 프래그먼트가 화면으로 슬라이드되고 뒤로를 누르면 화면 밖으로 슬라이드되는지 확인합니다.

작업

탐색 시스템을 사용하면 작업을 통해 탐색할 수도 있습니다. 앞에서 언급한 대로 탐색 그래프에 표시된 선은 작업을 시각적으로 나타낸 것입니다.

작업별 탐색은 대상별 탐색과 비교해 다음과 같은 이점이 있습니다.

  • 앱에서 탐색 경로를 시각화할 수 있습니다.
  • 작업에는 전환 애니메이션, 인수 값, 백 스택 동작 등 개발자가 설정할 수 있는 추가 연결 속성이 포함될 수 있습니다.
  • safe args 플러그인을 사용하여 탐색할 수 있으며 이 내용은 잠시 후에 알아봅니다.

다음은 flow_step_one_destflow_step_two_dest를 연결하는 작업의 시각화 및 XML입니다.

<fragment
    android:id="@+id/flow_step_one_dest"
    android:name="com.example.android.codelabs.navigation.FlowStepFragment">

    <argument
        .../>

    <action
        android:id="@+id/next_action"
        app:destination="@+id/flow_step_two_dest">
    </action>
</fragment>

<fragment
    android:id="@+id/flow_step_two_dest"
    android:name="com.example.android.codelabs.navigation.FlowStepFragment">
    <!-- ...removed for simplicity-->
</fragment>

참고:

  • 작업은 대상 내에 중첩됩니다. 이 대상에서 이동합니다.
  • 작업에는 flow_step_two_dest를 참조하는 대상 인수가 포함됩니다. 이 ID가 이동할 위치의 ID입니다.
  • 작업 ID는 'next_action'입니다.

다음은 flow_step_two_desthome_dest에 연결하는 작업의 또 다른 예입니다.

<fragment
    android:id="@+id/home_dest"
    android:name="com.example.android.codelabs.navigation.HomeFragment"
    .../>

<fragment
    android:id="@+id/flow_step_two_dest"
    android:name="com.example.android.codelabs.navigation.FlowStepFragment">

    <argument
        .../>

    <action
        android:id="@+id/next_action"
        app:popUpTo="@id/home_dest">
    </action>
</fragment>

참고:

  • 동일한 ID next_action이 flow_step_two_desthome_dest에 연결하는 작업에 사용됩니다. next_action ID를 사용하여 flow_step_one_destflow_step_two_dest에서 탐색할 수 있습니다. 이는 작업이 추상화 수준을 제공하고 컨텍스트에 따라 개발자를 다른 위치로 이동할 수 있는 방법에 관한 예입니다.
  • popUpTo 속성이 사용됩니다. 이 작업은 개발자가 home_dest에 도달할 때까지 백 스택에서 프래그먼트를 표시합니다.

이제 Navigate with Action 버튼을 연결하여 사용해 봅니다.

1. Design 모드에서 mobile_navigation.xml 파일을 엽니다.

2. 화살표를 home_dest에서 flow_step_one_dest로 드래그합니다.

3. 작업 화살표를 선택(파란색)한 상태에서 작업 속성을 다음과 같이 변경합니다.

  • ID = next_action
  • Enter 전환 = slide_in_right
  • Exit 전환 = slide_out_left
  • Pop Enter 전환 = slide_in_left
  • Pop Exit 전환 = slide_out_right

4. Text 탭을 클릭합니다.

home_dest 대상에서 새로 추가된 next_action 작업에 유의합니다.

mobile_navigation.xml

<fragment android:id="@+id/home_dest"
        ...>

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

5. HomeFragment.kt를 엽니다.

6. navigate_action_button에 클릭 리스너를 추가합니다.

HomeFragment.kt

 view.findViewById<Button>(R.id.navigate_action_button)?.setOnClickListener(
        Navigation.createNavigateOnClickListener(R.id.next_action, null)
)

7. 이제 Navigate to Action을 탭하면 다음 화면으로 이동하는지 확인합니다.

Safe Args

탐색 구성요소에는 대상 및 작업에 지정된 인수에 유형 안전 액세스를 하기 위해 간단한 객체 및 빌더 클래스를 생성하는 safe args라는 Gradle 플러그인이 있습니다.

safe args를 사용하면 대상 간에 값을 전달할 때 다음과 같은 코드를 제거할 수 있습니다.

val username = arguments?.getString("usernameKey")

대신 setter와 getter를 생성한 코드로 교체합니다.

val username = args.username

safe args를 사용하여 값 전달

1. 프로젝트 build.gradle 파일을 열고 safe args 플러그인을 확인합니다.

build.gradle

dependencies {
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigationVersion"
    //...
    }

2. app/build.gradle 파일을 열고 적용된 플러그인을 확인합니다.

app/build.gradle

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'androidx.navigation.safeargs.kotlin'

android {
   //...
}

3. mobile_navigation.xml,을 열고 flow_step_one_dest 대상에서 인수가 정의되는 방식을 확인합니다.

mobile_navigation.xml

<fragment
    android:id="@+id/flow_step_one_dest"
    android:name="com.example.android.codelabs.navigation.FlowStepFragment"
    tools:layout="@layout/flow_step_one_fragment">
    <argument
        android:name="flowStepNumber"
        app:argType="integer"
        android:defaultValue="1"/>

    <action...>
    </action>
</fragment>

<argument> 태그를 사용하여 safeargs는 FlowStepFragmentArgs라는 클래스를 생성합니다.

XML에는 android:name="flowStepNumber"로 지정된 flowStepNumber라는 인수가 포함되어 있으므로 생성된 FlowStepFragmentArgs 클래스에는 getter와 setter가 있는 flowStepNumber 변수가 포함됩니다.

4. FlowStepFragment.kt를 엽니다.

5. 아래에 표시된 코드 줄을 주석 처리합니다.

FlowStepFragment.kt

// Comment out this line
// val flowStepNumber = arguments?.getInt("flowStepNumber")

이전 스타일의 이 코드는 유형에 안전하지 않습니다. safe args를 사용하는 것이 더 좋습니다.

6. 코드 생성 클래스 FlowStepFragmentArgs를 사용하도록 FlowStepFragment를 업데이트합니다. 이렇게 하면 유형에 안전한 방식으로 FlowStepFragment 인수를 가져옵니다.

FlowStepFragment.kt

val safeArgs: FlowStepFragmentArgs by navArgs()
val flowStepNumber = safeArgs.flowStepNumber

Safe Args 방향 클래스

safe args를 사용하여 인수 추가 여부와 상관없이 유형에 안전한 방식으로 탐색할 수도 있습니다. 생성된 방향 클래스를 사용하면 됩니다.

방향 클래스는 작업이 있는 모든 개별 대상에 생성됩니다. 방향 클래스에는 대상에 있는 모든 작업의 메서드가 포함되어 있습니다.

예를 들어 HomeFragment.kt의 navigate_action_button 클릭 리스너는 다음과 같이 변경될 수 있습니다.

HomeFragment.kt

// Note the usage of curly braces since we are defining the click listener lambda
view.findViewById<Button>(R.id.navigate_action_button)?.setOnClickListener {
    val flowStepNumberArg = 1
    val action = HomeFragmentDirections.nextAction(flowStepNumberArg)
    findNavController().navigate(action)
}

탐색 구성요소에는 NavigationUI 클래스와 navigation-ui-ktx kotlin 확장 프로그램이 포함되어 있습니다. NavigationUI에는 메뉴 항목을 탐색 대상과 연결하는 정적 메서드가 있고 navigation-ui-ktx는 동일한 작업을 실행하는 확장 함수 모음입니다. NavigationUI가 현재 그래프에서 대상과 ID가 동일한 메뉴 항목을 찾으면 그 대상으로 이동하도록 메뉴 항목을 구성합니다.

옵션 메뉴와 함께 NavigationUI 사용

가장 쉽게 NavigationUI를 사용하는 방법 중 하나는 옵션 메뉴 설정을 간소화하는 것입니다. 특히 NavigationUI는 onOptionsItemSelected 콜백 처리를 간소화합니다.

1. MainActivity.kt를 엽니다.

onCreateOptionsMenu에서 overflow_menu 메뉴를 확장하는 코드가 어떻게 이미 있는지 확인합니다.

2. res/menu/overflow_menu.xml을 엽니다.

3. settings_dest를 포함하도록 더보기 메뉴를 업데이트합니다.

overflow_menu.xml

<item
    android:id="@+id/settings_dest"
    android:icon="@drawable/ic_settings"
    android:menuCategory="secondary"
    android:title="@string/settings" />

4. MainActivity.kt를 엽니다.

5. onNavDestinationSelected 도우미 메서드로 NavigationUI가 onOptionsItemSelected를 처리하도록 합니다. 메뉴 항목의 목적이 탐색이 아닌 경우 super.onOptionsItemSelected로 처리합니다.

MainActivity.kt

override fun onOptionsItemSelected(item: MenuItem): Boolean {
    return item.onNavDestinationSelected(findNavController(R.id.my_nav_host_fragment))
            || super.onOptionsItemSelected(item)
}

6. 앱을 실행합니다. SettingsFragment로 이동하는 작동 ActionBar 메뉴가 있어야 합니다.

NavigationUI를 사용하여 하단 탐색 메뉴 구성

코드에는 이미 하단 탐색 메뉴를 구현하는 XML 레이아웃 코드가 포함되어 있어 하단 탐색 메뉴가 표시됩니다. 그러나 어디로도 이동하지 않습니다.

1. res/layout/navigation_activity/navigation_activity.xml (h470dp)을 열고 Text 탭을 클릭합니다.

하단 탐색 메뉴의 XML 레이아웃 코드가 어떻게 그 위치에 있고 bottom_nav_menu.xml을 참조하는지 확인합니다.

<com.google.android.material.bottomnavigation.BottomNavigationView
    android:id="@+id/bottom_nav_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:menu="@menu/bottom_nav_menu" />

2. res/menu/bottom_nav_menu.xml을 엽니다.

하단 탐색 메뉴에 어떻게 항목이 두 개 있는지 그리고 탐색 그래프 대상의 대상과 항목 ID가 일치하는지 확인합니다.

bottom_nav_menu.xml

<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@id/home_dest"
        android:icon="@drawable/ic_home"
        android:title="@string/home" />
    <item
        android:id="@id/deeplink_dest"
        android:icon="@drawable/ic_android"
        android:title="@string/deeplink" />
</menu>

하단 탐색 메뉴에서 NavigationUI를 사용하여 실제로 작업을 실행하도록 해 보겠습니다.

3. MainActivity.kt를 엽니다.

4. setupWithNavController(bottomNavigationView: BottomNavigationView, navController: NavController)를 사용하여 setupBottomNavMenu 메서드를 구현합니다.

MainActivity.kt

private fun setupBottomNavMenu(navController: NavController) {
    val bottomNav = findViewById<BottomNavigationView>(R.id.bottom_nav_view)
    bottomNav?.setupWithNavController(navController)
}

이제 하단 탐색 메뉴가 작동합니다.

NavigationUI를 사용하여 탐색 창 구성

마지막으로 NavigationUI를 사용하여 ActionBar 및 적절한 위로 탐색 처리 등 측면 탐색 메뉴와 탐색 창을 구성해 보겠습니다. 화면이 충분히 크거나 하단 탐색 메뉴를 표시하기에 화면이 너무 짧은 경우 표시됩니다.

먼저 적절한 레이아웃 XML 코드가 어떻게 이미 앱에 있는지 관찰합니다.

1. navigation_activity.xmlnavigation_activity.xml (w960dp)을 모두 엽니다.

두 레이아웃에 모두 nav_drawer_menu에 연결된 NavigationView가 어떻게 포함되어 있는지 확인합니다. 태블릿 버전(w960dp)에서 NavigationView는 항상 화면에 표시됩니다. 더 작은 기기에서는 NavigationView가 DrawerLayout 내에 중첩됩니다.

이제 NavigationView 탐색 구현을 시작합니다.

2. MainActivity.kt를 엽니다.

3. setupWithNavController(navigationView: NavigationView, navController: NavController)를 사용하여 setupNavigationMenu 메서드를 구현합니다. 이 버전의 메서드가 BottomNavigationView가 아닌 NavigationView를 사용하는 방식을 확인합니다.

MainActivity.kt

private fun setupNavigationMenu(navController: NavController) {
    val sideNavView = findViewById<NavigationView>(R.id.nav_view)
    sideNavView?.setupWithNavController(navController)
}

이제 탐색 뷰 메뉴가 화면에 표시되지만 ActionBar에는 영향을 미치지 않습니다.

ActionBar를 설정하려면 AppBarConfiguration의 인스턴스를 만들어야 합니다. AppBarConfiguration의 목적은 툴바, 접기 방식 툴바, 작업 모음에 원하는 구성 옵션을 지정하는 것입니다. 구성 옵션에는 작업 모음이 창 레이아웃을 처리해야 하는지와 어떤 대상을 최상위 대상으로 간주하는지에 관한 내용이 포함됩니다.

최상위 대상은 앱의 루트 수준 대상입니다. 이러한 대상은 앱 바에 '위로' 버튼을 표시하지 않고 대상이 창 레이아웃을 사용하는 경우 창 아이콘을 표시합니다.

4. 일련의 최상위 대상 ID와 창 레이아웃을 전달하여 AppBarConfiguration을 만듭니다.

MainActivity.kt

val drawerLayout : DrawerLayout? = findViewById(R.id.drawer_layout)
appBarConfiguration = AppBarConfiguration(
        setOf(R.id.home_dest, R.id.deeplink_dest),
        drawerLayout)

이제 AppBarConfiguration이 있으므로 NavigationUI.setupActionBarWithNavController를 호출할 수 있습니다. 그러면 다음 작업이 실행됩니다.

  • 대상의 라벨에 따라 ActionBar에 제목 표시
  • 최상위 대상에 없을 때마다 위로 버튼 표시
  • 최상위 대상에 있을 때 창 아이콘(햄버거 아이콘) 표시

5. setupActionBarWithNavController를 구현합니다.

MainActivity.kt

private fun setupActionBar(navController: NavController,
                           appBarConfig : AppBarConfiguration) {
    setupActionBarWithNavController(navController, appBarConfig)
}

위로 버튼을 누를 때 발생하는 작업을 NavigationUI가 처리하도록 해야 합니다.

6. 동일한 AppBarConfiguration을 사용하여 onSupportNavigationUp을 재정의하고 NavigationUI.navigateUp을 호출합니다.

MainActivity.kt

override fun onSupportNavigateUp(): Boolean {
    return findNavController(R.id.my_nav_host_fragment).navigateUp(appBarConfiguration)
}

7. 코드를 실행합니다. 화면 분할 모드에서 앱을 열면 작동하는 탐색 창이 있어야 합니다. 위로 아이콘과 창 아이콘이 적절한 시점에 표시되고 올바르게 작동해야 합니다.

새 대상을 NavigationView에 추가하는 것은 간단합니다. 탐색 창이 위로 및 뒤로 탐색과 함께 작동하도록 한 후 새 메뉴 항목을 추가하면 됩니다.

8. menu/nav_drawer_menu.xml을 엽니다.

9. settings_dest의 새 메뉴 항목을 추가합니다.

<item
    android:id="@+id/settings_dest"
    android:icon="@drawable/ic_settings"
    android:title="@string/settings" />

이제 탐색 창에 설정 화면이 대상으로 표시됩니다. 훌륭합니다.

탐색 구성요소에는 딥 링크 지원도 포함됩니다. 딥 링크는 실제 URL 링크에서 온 것이든 알림의 대기 중인 인텐트에서 온 것이든 앱 탐색의 중간으로 이동하는 방법입니다.

딥 링크 처리에 탐색 라이브러리를 사용하는 한 가지 이점은 사용자가 앱 위젯이나 알림, 웹 링크 등 다른 시작 지점에서 적절한 백 스택으로 올바른 대상에서 시작할 수 있다는 것입니다(다음 단계에서 설명).

탐색은 NavDeepLinkBuilder 클래스를 제공하여 사용자를 특정 대상으로 안내하는 PendingIntent를 생성합니다.

NavDeepLinkBuilder를 사용하여 앱 위젯을 대상에 연결합니다.

1. DeepLinkAppWidgetProvider.kt를 엽니다.

2. NavDeepLinkBuilder로 생성된 PendingIntent를 추가합니다.

DeepLinkAppWidgetProvider

val args = Bundle()
args.putString("myarg", "From Widget");
val pendingIntent = NavDeepLinkBuilder(context)
        .setGraph(R.navigation.mobile_navigation)
        .setDestination(R.id.deeplink_dest)
        .setArguments(args)
        .createPendingIntent()

remoteViews.setOnClickPendingIntent(R.id.deep_link_button, pendingIntent)

참고:

  • setGraph에는 탐색 그래프가 포함됩니다.
  • setDestination은 링크가 연결되는 위치를 지정합니다.
  • setArguments에는 딥 링크로 전달하려는 인수가 포함됩니다.

3. 홈 화면에 딥 링크 위젯을 추가합니다. 홈 화면을 길게 탭하여 위젯 추가 옵션을 확인합니다.

길게 탭하기

아래로 스크롤하여 위젯 찾기

완료되면 딥 링크 위젯이 표시됩니다.

4. 위젯을 탭하여 Android 대상이 올바른 인수로 열리는지 확인합니다. 상단에 'From Widget'이 표시되어야 합니다. DeepLinkAppWidgetProvider에서 전달한 인수이기 때문입니다.

5. 뒤로 버튼을 누르면 home_dest 대상으로 이동하는지 확인합니다.

딥 링크의 백 스택은 개발자가 전달하는 탐색 그래프를 사용하여 결정됩니다. 개발자가 선택한 명시적인 활동에 상위 활동이 있으면 이러한 상위 활동도 포함됩니다.

백 스택은 app:startDestination으로 지정된 대상을 사용하여 생성됩니다. 이 앱에는 활동 하나와 탐색 수준이 하나뿐이므로 백 스택을 통해 home_dest 대상으로 이동합니다.

좀 더 복잡한 탐색에는 중첩된 탐색 그래프가 포함될 수 있습니다. 중첩된 그래프의 각 수준에 있는 app:startDestination이 백 스택을 결정합니다. 딥 링크와 중첩된 그래프에 관한 자세한 내용은 탐색 원리를 참고하세요.

딥 링크의 가장 일반적인 용도 중 하나는 웹 링크가 앱에서 활동을 열도록 허용하는 것입니다. 일반적으로 인텐트 필터를 사용하고 URL을 개발자가 열려는 활동과 연결합니다.

탐색 라이브러리를 사용하면 이와 같은 작업이 매우 간단해지고 URL을 탐색 그래프의 대상에 직접 매핑할 수 있습니다.

<deepLink>는 그래프의 대상에 추가할 수 있는 요소입니다. 각 <deepLink> 요소에는 단일 필수 속성 app:uri가 있습니다.

직접 URI 일치 외에도 다음 기능이 지원됩니다.

  • 스키마가 없는 URI는 http와 https로 간주됩니다. 예를 들어 www.example.comhttp://www.example.comhttps://www.example.com과 일치합니다.
  • {placeholder_name} 형식의 자리표시자를 사용하여 하나 이상의 문자와 일치시킬 수 있습니다. 자리표시자의 문자열 값은 같은 이름의 키가 있는 인수 번들에서 사용할 수 있습니다. 예를 들어 http://www.example.com/users/{id}http://www.example.com/users/4와 일치합니다.
  • .* 와일드 카드를 사용하면 0개 이상의 문자와 일치시킬 수 있습니다.
  • NavController는 ACTION_VIEW 인텐트를 자동으로 처리하고 일치하는 딥 링크를 찾습니다.

이 단계에서는 딥 링크를 www.example.com에 추가합니다.

1. mobile_navigation.xml을 엽니다.

2. <deepLink> 요소를 deeplink_dest 대상에 추가합니다.

mobile_navigation.xml

<fragment
    android:id="@+id/deeplink_dest"
    android:name="com.example.android.codelabs.navigation.DeepLinkFragment"
    android:label="@string/deeplink"
    tools:layout="@layout/deeplink_fragment">

    <argument
        android:name="myarg"
        android:defaultValue="Android!"/>

    <deepLink app:uri="www.example.com/{myarg}" />
</fragment>

3. AndroidManifest.xml을 엽니다.

4. nav-graph 태그를 추가합니다. 이렇게 하면 적절한 인텐트 필터가 생성됩니다.

AndroidManifest.xml

<activity android:name=".MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>

    <nav-graph android:value="@navigation/mobile_navigation" />
</activity>

5. 딥 링크를 사용하여 앱을 실행합니다. 이 작업을 실행하는 두 가지 방법은 다음과 같습니다.

  • adb를 사용합니다.
adb shell am start -a android.intent.action.VIEW -d "http://www.example.com/urlTest" 
  • Google 앱을 통해 탐색합니다. 검색창에 www.example.com/urlTest를 입력할 수 있고 명확성 창이 표시됩니다. Navigation codelab을 선택합니다.

검색창을 사용하여 열림

(Chrome에서가 아님)

명확성 대화상자

어느 방법을 사용하든지 화면에 'urlTest' 메시지가 표시됩니다. URL에서 프래그먼트로 전달되었습니다.

Codelab 앱에는 실험할 부분이 하나 더 있습니다. 바로 장바구니 버튼입니다.

다음은 이 Codelab에서 배운 내용을 요약한 내용입니다. 이 단계에는 주석이 포함되어 있지 않으므로 직접 시도해 보세요.

  1. 새 프래그먼트 클래스 만들기
  2. 프래그먼트를 탐색 그래프에 대상으로 추가하기
  3. 장바구니 아이콘으로 새 프래그먼트 클래스를 열고 NavigationUI를 사용하여 메뉴 처리하기

탐색 구성요소의 기본 개념을 잘 알게 되었습니다. 이 Codelab에서는 다음 항목을 알아봤습니다.

  • 탐색 그래프 구조
  • NavHostFragment 및 NavController
  • 특정 대상으로 이동하는 방법
  • 작업별로 탐색하는 방법
  • 새 safeargs 플러그인 사용을 비롯하여 대상 간에 인수를 전달하는 방법
  • 메뉴, 하단 탐색 메뉴, 탐색 창을 사용하여 탐색
  • 딥 링크를 통해 탐색


이 앱을 계속 살펴보거나 자체 앱에서 탐색을 사용할 수 있습니다.

다음을 비롯하여 더 많은 것을 시도해 볼 수 있습니다.

  • 백 스택(또는 백 스택 조작)에서 대상 제거
  • 중첩된 탐색 그래프
  • 조건부 탐색
  • 새 대상 지원 추가

탐색 구성요소에 관한 자세한 내용은 문서를 참고하세요. 다른 아키텍처 구성요소에 관해 알아보려면 다음 Codelab을 사용해 보세요.