NavigationUI로 UI 구성요소 업데이트

탐색 구성요소에는 NavigationUI 클래스가 포함되어 있습니다. 이 클래스에는 상단 앱 바, 탐색 창 및 하단 탐색으로 탐색을 관리하는 정적 메서드가 포함되어 있습니다.

상단 앱 바

상단 앱 바는 현재 화면의 정보와 작업을 표시하기 위해 앱의 상단을 따라 일관된 위치를 제공합니다.

상단 앱 바가 표시된 화면
그림 1. 상단 앱 바가 표시된 화면

NavigationUI에는 사용자가 앱을 탐색할 때 상단 앱 바의 콘텐츠를 자동으로 업데이트하는 메서드가 포함되어 있습니다. 예를 들어 NavigationUI는 탐색 그래프의 대상 라벨을 사용하여 상단 앱 바의 제목을 최신 상태로 유지합니다.

<navigation>
    <fragment ...
              android:label="Page title">
      ...
    </fragment>
</navigation>

아래에 설명된 상단 앱 바 구현과 함께 NavigationUI를 사용할 때 라벨의 {argName} 형식을 사용하여 대상에 제공된 인수에서 대상에 첨부된 라벨을 자동으로 채울 수 있습니다.

NavigationUI는 다음과 같은 상단 앱 바 유형을 지원합니다.

앱 바에 관한 자세한 내용은 앱 바 설정을 참고하세요.

AppBarConfiguration

NavigationUIAppBarConfiguration 객체를 사용하여 앱 표시 영역의 왼쪽 상단 모서리에 있는 탐색 버튼의 동작을 관리합니다. 탐색 버튼의 동작은 사용자가 최상위 수준 대상에 있는지 여부에 따라 달라집니다.

최상위 수준 대상은 계층 구조의 관련 대상 집합에 있는 루트 또는 최고 수준 대상입니다. 최상위 수준 대상은 이보다 상위 수준의 대상이 없기 때문에 상단 앱 바에 '위로' 버튼을 표시하지 않습니다. 기본적으로 앱의 시작 대상은 유일한 최상위 수준 대상입니다.

사용자가 최상위 수준 대상에 있을 때 대상이 DrawerLayout을 사용하면 탐색 버튼은 창 아이콘 이 됩니다. 대상이 DrawerLayout을 사용하지 않으면 탐색 버튼이 숨겨집니다. 사용자가 다른 대상에 있을 때 탐색 버튼은 '위로' 버튼 으로 표시됩니다. 시작 대상만 최상위 수준 대상으로 사용하여 탐색 버튼을 구성하려면 다음과 같이 AppBarConfiguration 객체를 만들어 해당하는 탐색 그래프에 전달합니다.

Kotlin

val appBarConfiguration = AppBarConfiguration(navController.graph)

자바

AppBarConfiguration appBarConfiguration =
        new AppBarConfiguration.Builder(navController.getGraph()).build();

경우에 따라 기본 시작 대상을 사용하는 대신 여러 최상위 수준 대상을 정의해야 할 수도 있습니다. 이와 관련한 일반적인 사용 사례는 BottomNavigationView를 사용하는 것입니다. 이 사용 사례에서는 서로 계층적으로 관련이 없으며 각각 고유한 관련 대상 집합을 포함할 수 있는 동위 화면이 있을 수 있습니다. 이 같은 경우에는 아래와 같이 대상 ID 집합을 생성자에 대신 전달할 수 있습니다.

Kotlin

val appBarConfiguration = AppBarConfiguration(setOf(R.id.main, R.id.profile))

자바

AppBarConfiguration appBarConfiguration =
        new AppBarConfiguration.Builder(R.id.main, R.id.profile).build();

툴바 만들기

NavigationUI로 툴바를 만들려면 먼저 아래와 같이 기본 활동에 바를 정의합니다.

<LinearLayout>
    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar" />
    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_fragment"
        ... />
    ...
</LinearLayout>

그런 다음, 아래 예와 같이 기본 활동의 onCreate() 메서드에서 setupWithNavController()를 호출합니다.

Kotlin

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

    ...

    val navController = findNavController(R.id.nav_host_fragment)
    val appBarConfiguration = AppBarConfiguration(navController.graph)
    findViewById<Toolbar>(R.id.toolbar)
        .setupWithNavController(navController, appBarConfiguration)
}

자바

@Override
protected void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.activity_main);

    ...

    NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
    AppBarConfiguration appBarConfiguration =
            new AppBarConfiguration.Builder(navController.getGraph()).build();
    Toolbar toolbar = findViewById(R.id.toolbar);
    NavigationUI.setupWithNavController(
            toolbar, navController, appBarConfiguration);
}

모든 대상에서 탐색 버튼이 위로 버튼으로 표시되도록 구성하려면 AppBarConfiguration을 빌드할 때 최상위 수준 대상에 대해 빈 상태의 대상 ID 집합을 전달합니다. 이는 모든 대상에서 두 번째 활동에 Toolbar의 위로 버튼이 표시되어야 하는 경우 유용합니다. 그러면 사용자는 백 스택에 다른 대상이 없을 때 상위 활동으로 돌아갈 수 있습니다. 다음 예와 같이 setFallbackOnNavigateUpListener()를 사용하여 navigateUp()이 그 외에 아무런 동작도 하지 않을 때의 대체 동작을 제어할 수 있습니다.

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    ...

    val navHostFragment =
        supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    val navController = navHostFragment.navController
    val appBarConfiguration = AppBarConfiguration(
        topLevelDestinationIds = setOf(),
        fallbackOnNavigateUpListener = ::onSupportNavigateUp
    )
    findViewById<Toolbar>(R.id.toolbar)
        .setupWithNavController(navController, appBarConfiguration)
}

자바

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...

    NavHostFragment navHostFragment = (NavHostFragment) supportFragmentManager.findFragmentById(R.id.nav_host_fragment);
    NavController navController = navHostFragment.getNavController();
    AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder()
        .setFallbackOnNavigateUpListener(::onSupportNavigateUp)
        .build();
    Toolbar toolbar = findViewById(R.id.toolbar);
    NavigationUI.setupWithNavController(
            toolbar, navController, appBarConfiguration);
}

CollapsingToolbarLayout 포함

툴바로 CollapsingToolbarLayout을 포함하려면 먼저 아래와 같이 활동에 툴바와 주변 레이아웃을 정의합니다.

<LinearLayout>
    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="@dimen/tall_toolbar_height">

        <com.google.android.material.appbar.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:contentScrim="?attr/colorPrimary"
            app:expandedTitleGravity="top"
            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">

            <androidx.appcompat.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"/>
        </com.google.android.material.appbar.CollapsingToolbarLayout>
    </com.google.android.material.appbar.AppBarLayout>

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_fragment"
        ... />
    ...
</LinearLayout>

그런 다음, 아래와 같이 기본 활동의 onCreate 메서드에서 setupWithNavController()를 호출합니다.

Kotlin

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

    ...

    val layout = findViewById<CollapsingToolbarLayout>(R.id.collapsing_toolbar_layout)
    val toolbar = findViewById<Toolbar>(R.id.toolbar)
    val navHostFragment =
        supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    val navController = navHostFragment.navController
    val appBarConfiguration = AppBarConfiguration(navController.graph)
    layout.setupWithNavController(toolbar, navController, appBarConfiguration)
}

자바

@Override
protected void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.activity_main);

    ...

    CollapsingToolbarLayout layout = findViewById(R.id.collapsing_toolbar_layout);
    Toolbar toolbar = findViewById(R.id.toolbar);
    NavHostFragment navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment);
    NavController navController = navHostFragment.getNavController();
    AppBarConfiguration appBarConfiguration =
            new AppBarConfiguration.Builder(navController.getGraph()).build();
    NavigationUI.setupWithNavController(layout, toolbar, navController, appBarConfiguration);
}

작업 모음

기본 작업 모음에 탐색 지원을 추가하려면 아래와 같이 기본 활동의 onCreate() 메서드에서 setupActionBarWithNavController()를 호출합니다. AppBarConfigurationonSupportNavigateUp()을 재정의할 때도 사용하므로 onCreate() 외부에 선언해야 합니다.

Kotlin

private lateinit var appBarConfiguration: AppBarConfiguration

...

override fun onCreate(savedInstanceState: Bundle?) {
    ...

    val navHostFragment =
        supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    val navController = navHostFragment.navController
    appBarConfiguration = AppBarConfiguration(navController.graph)
    setupActionBarWithNavController(navController, appBarConfiguration)
}

자바

AppBarConfiguration appBarConfiguration;

...

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...

    NavHostFragment navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment);
    NavController navController = navHostFragment.getNavController();
    appBarConfiguration = new AppBarConfiguration.Builder(navController.getGraph()).build();
    NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
}

그런 다음, onSupportNavigateUp()을 재정의하여 상단 탐색을 처리합니다.

Kotlin

override fun onSupportNavigateUp(): Boolean {
    val navController = findNavController(R.id.nav_host_fragment)
    return navController.navigateUp(appBarConfiguration)
            || super.onSupportNavigateUp()
}

자바

@Override
public boolean onSupportNavigateUp() {
    NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
    return NavigationUI.navigateUp(navController, appBarConfiguration)
            || super.onSupportNavigateUp();
}

앱 바 변형 지원

활동에 상단 앱 바를 추가하는 것은 앱 바의 레이아웃이 앱의 각 대상에서 유사할 때 잘 작동합니다. 그러나 상단 앱 바가 대상 전반에서 상당히 많이 변경되면 상단 앱 바를 활동에서 삭제하고 대신 각 대상 프래그먼트에서 정의하는 것이 좋습니다.

예를 들어 그림 2와 같이 대상 중 하나는 표준 Toolbar를 사용하지만 다른 대상은 AppBarLayout을 사용하여 탭이 있는 더 복잡한 앱 바를 만들 수 있습니다.

두 가지 상단 앱 바 변형: 표준 툴바(왼쪽) 및 툴바와 탭이 있는 AppBarLayout(오른쪽)
그림 2. 두 가지 앱 바 변형: 표준 Toolbar(왼쪽), Toolbar 및 탭이 있는 AppBarLayout(오른쪽)

NavigationUI를 사용하여 대상 프래그먼트 내에 이 예를 구현하려면 먼저 표준 툴바를 사용하는 대상 프래그먼트부터 시작하여 각 프래그먼트 레이아웃에 앱 바를 정의합니다.

<LinearLayout>
    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        ... />
    ...
</LinearLayout>

다음으로, 탭이 있는 앱 바를 사용하는 대상 프래그먼트를 정의합니다.

<LinearLayout>
    <com.google.android.material.appbar.AppBarLayout
        ... />

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            ... />

        <com.google.android.material.tabs.TabLayout
            ... />

    </com.google.android.material.appbar.AppBarLayout>
    ...
</LinearLayout>

탐색 구성 로직은 활동에서 초기화하는 대신 각 프래그먼트의 onViewCreated() 메서드 내에서 setupWithNavController()를 호출해야 한다는 점을 제외하면 이 두 프래그먼트 모두에서 동일합니다.

Kotlin

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    val navController = findNavController()
    val appBarConfiguration = AppBarConfiguration(navController.graph)

    view.findViewById<Toolbar>(R.id.toolbar)
            .setupWithNavController(navController, appBarConfiguration)
}

자바

@Override
public void onViewCreated(@NonNull View view,
                          @Nullable Bundle savedInstanceState) {
    NavController navController = Navigation.findNavController(view);
    AppBarConfiguration appBarConfiguration =
            new AppBarConfiguration.Builder(navController.getGraph()).build();
    Toolbar toolbar = view.findViewById(R.id.toolbar);

    NavigationUI.setupWithNavController(
            toolbar, navController, appBarConfiguration);
}

메뉴 항목에 대상 연결

NavigationUI는 대상을 메뉴 기반 UI 구성요소에 연결하기 위한 도우미도 제공합니다. NavigationUI에는 연결된 대상을 호스팅하는 NavController와 함께 MenuItem을 받는 도우미 메서드 onNavDestinationSelected()가 포함되어 있습니다. MenuItemid가 대상의 id와 일치하면 NavController가 그 대상으로 이동할 수 있습니다.

예를 들어 아래의 XML 스니펫에서는 공통 id, details_page_fragment가 있는 메뉴 항목과 대상을 정의합니다.

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

    ...

    <fragment android:id="@+id/details_page_fragment"
         android:label="@string/details"
         android:name="com.example.android.myapp.DetailsFragment" />
</navigation>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    ...

    <item
        android:id="@+id/details_page_fragment"
        android:icon="@drawable/ic_details"
        android:title="@string/details" />
</menu>

예를 들어 메뉴가 활동의 onCreateOptionsMenu()를 통해 추가되었다면 다음 예와 같이 활동의 onOptionsItemSelected()를 재정의하여 onNavDestinationSelected()를 호출함으로써 메뉴 항목을 대상과 연결할 수 있습니다.

Kotlin

override fun onOptionsItemSelected(item: MenuItem): Boolean {
    val navController = findNavController(R.id.nav_host_fragment)
    return item.onNavDestinationSelected(navController) || super.onOptionsItemSelected(item)
}

자바

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
    return NavigationUI.onNavDestinationSelected(item, navController)
            || super.onOptionsItemSelected(item);
}

이제 사용자가 details_page_fragment 메뉴 항목을 클릭하면 앱이 id가 동일한 해당 대상으로 자동으로 이동합니다.

탐색 창 추가

탐색 창은 앱의 기본 탐색 메뉴를 표시하는 UI 패널입니다. 사용자가 앱 바에서 창 아이콘 을 터치하거나 화면의 왼쪽 가장자리에서 손가락을 스와이프하면 창이 표시됩니다.

탐색 메뉴를 표시하는 열린 창
그림 3. 탐색 메뉴를 표시하는 열린 창

창 아이콘은 DrawerLayout을 사용하는 모든 최상위 수준 대상에 표시됩니다.

탐색 창을 추가하려면 먼저 DrawerLayout을 루트 뷰로 선언합니다. DrawerLayout 내부에 기본 UI 콘텐츠의 레이아웃과 탐색 창의 콘텐츠가 포함된 다른 뷰를 추가합니다.

예를 들어 다음 레이아웃은 2개의 하위 뷰(기본 콘텐츠를 포함하기 위한 NavHostFragment 및 탐색 창의 콘텐츠를 위한 NavigationView)가 있는 DrawerLayout을 사용합니다.

<?xml version="1.0" encoding="utf-8"?>
<!-- Use DrawerLayout as root container for activity -->
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <!-- Layout to contain contents of main body of screen (drawer will slide over this) -->
    <androidx.fragment.app.FragmentContainerView
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:id="@+id/nav_host_fragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_graph" />

    <!-- Container for contents of drawer - use NavigationView to make configuration easier -->
    <com.google.android.material.navigation.NavigationView
        android:id="@+id/nav_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:fitsSystemWindows="true" />

</androidx.drawerlayout.widget.DrawerLayout>

다음으로, 아래 예와 같이 DrawerLayoutAppBarConfiguration에 전달하여 탐색 그래프에 연결합니다.

Kotlin

val appBarConfiguration = AppBarConfiguration(navController.graph, drawerLayout)

자바

AppBarConfiguration appBarConfiguration =
        new AppBarConfiguration.Builder(navController.getGraph())
            .setDrawerLayout(drawerLayout)
            .build();

그런 다음, 아래와 같이 기본 활동 클래스에서 기본 활동의 onCreate() 메서드로부터 setupWithNavController()를 호출합니다.

Kotlin

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

    ...

    val navHostFragment =
        supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    val navController = navHostFragment.navController
    findViewById<NavigationView>(R.id.nav_view)
        .setupWithNavController(navController)
}

자바

@Override
protected void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.activity_main);

    ...

    NavHostFragment navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment);
    NavController navController = navHostFragment.getNavController();
    NavigationView navView = findViewById(R.id.nav_view);
    NavigationUI.setupWithNavController(navView, navController);
}

하단 탐색

NavigationUI는 하단 탐색도 처리할 수 있습니다. 사용자가 메뉴 항목을 선택하면 NavControlleronNavDestinationSelected()를 호출하고 하단 탐색 메뉴에서 선택된 항목을 자동으로 업데이트합니다.

하단 탐색 메뉴
그림 4. 하단 탐색 메뉴

앱에 하단 탐색 메뉴를 만들려면 먼저 아래와 같이 기본 활동에 바를 정의합니다.

<LinearLayout>
    ...
    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_fragment"
        ... />
    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottom_nav"
        app:menu="@menu/menu_bottom_nav" />
</LinearLayout>

그런 다음, 아래와 같이 기본 활동 클래스에서 기본 활동의 onCreate() 메서드로부터 setupWithNavController()를 호출합니다.

Kotlin

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

    ...

    val navHostFragment =
        supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    val navController = navHostFragment.navController
    findViewById<BottomNavigationView>(R.id.bottom_nav)
        .setupWithNavController(navController)
}

자바

@Override
protected void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.activity_main);

    ...

    NavHostFragment navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment);
    NavController navController = navHostFragment.getNavController();
    BottomNavigationView bottomNav = findViewById(R.id.bottom_nav);
    NavigationUI.setupWithNavController(bottomNav, navController);
}

하단 탐색이 포함된 종합적인 예는 GitHub의 Android 아키텍처 구성요소 고급 탐색 샘플을 참고하세요.

탐색 이벤트 수신 대기

NavController와의 상호작용은 대상 간 탐색을 위한 기본 방법입니다. NavControllerNavHost의 콘텐츠를 새로운 대상으로 대체합니다. 대부분의 경우 UI 요소(예: 상단 앱 바 또는 BottomNavigationBar 같은 기타 영구 탐색 컨트롤)는 NavHost 외부에 존재하며 대상 간을 탐색할 때 업데이트되어야 합니다.

NavControllerNavController현재 대상 또는 그 인수가 변경될 때 호출되는 OnDestinationChangedListener 인터페이스를 제공합니다. 새 리스너는 addOnDestinationChangedListener() 메서드를 통해 등록될 수 있습니다. addOnDestinationChangedListener()를 호출할 때 현재 대상이 있다면 즉시 리스너로 전송됩니다.

NavigationUIOnDestinationChangedListener를 사용하여 이런 공통 UI 구성요소가 탐색을 인식하도록 합니다. 그러나 OnDestinationChangedListener를 자체적으로 사용하여 모든 맞춤 UI 또는 비즈니스 로직이 탐색 이벤트를 인식하도록 할 수도 있습니다.

예를 들어 앱의 일부 영역에서는 표시하고 다른 영역에서는 숨기려는 공통 UI 요소가 있을 수 있습니다. 다음 예에서와 같이 자체 OnDestinationChangedListener를 사용하면 타겟 대상에 따라 이러한 UI 요소를 선택적으로 표시하거나 숨길 수 있습니다.

Kotlin

navController.addOnDestinationChangedListener { _, destination, _ ->
   if(destination.id == R.id.full_screen_destination) {
       toolbar.visibility = View.GONE
       bottomNavigationView.visibility = View.GONE
   } else {
       toolbar.visibility = View.VISIBLE
       bottomNavigationView.visibility = View.VISIBLE
   }
}

자바

navController.addOnDestinationChangedListener(new NavController.OnDestinationChangedListener() {
   @Override
   public void onDestinationChanged(@NonNull NavController controller,
           @NonNull NavDestination destination, @Nullable Bundle arguments) {
       if(destination.getId() == R.id.full_screen_destination) {
           toolbar.setVisibility(View.GONE);
           bottomNavigationView.setVisibility(View.GONE);
       } else {
           toolbar.setVisibility(View.VISIBLE);
           bottomNavigationView.setVisibility(View.VISIBLE);
       }
   }
});

추가 리소스

탐색에 관한 자세한 내용은 다음 추가 리소스를 참고하세요.

샘플

Codelab

블로그 게시물

동영상