Navigation コンポーネントには、NavigationUI
クラスがあります。このクラスには、トップ アプリバーやナビゲーション ドロワー、ボトム ナビゲーションを使用してナビゲーションを管理する静的メソッドが含まれています。
トップ アプリバー
トップ アプリバーは、アプリの上部に沿って同じ場所に配置され、現在の画面に基づく情報やアクションを表示します。
NavigationUI
には、ユーザーがアプリ内を移動したときにトップ アプリバー内のコンテンツを自動的に更新するメソッドが含まれています。たとえば、NavigationUI
は、ナビゲーション グラフ内のデスティネーション ラベルを使用して、トップ アプリバーのタイトルを最新の状態に保ちます。
<navigation> <fragment ... android:label="Page title"> ... </fragment> </navigation>
NavigationUI
を使用して以下で説明するようなトップ アプリバーの実装を行った場合、デスティネーションにアタッチするラベルは、ラベル内で {argName}
形式を使用することで、デスティネーションに提供される引数に基づいて自動的に入力されます。
NavigationUI
は、以下のタイプのトップ アプリバーをサポートしています。
アプリバーの詳細については、アプリバーの設定をご覧ください。
AppBarConfiguration
NavigationUI
は、AppBarConfiguration
オブジェクトを使用して、アプリの表示領域の左上隅にあるナビゲーション ボタンの動作を管理します。ナビゲーション ボタンの動作は、ユーザーが「トップレベル デスティネーション」にいるかどうかで変わります。
トップレベル デスティネーションとは、互いに階層構造としての関係を持つデスティネーションの中で、ルートまたは最上位レベルにあるデスティネーションのことです。トップレベル デスティネーションでは、それよりも上位にデスティネーションがないため、トップ アプリバーに「上へ」ボタンが表示されません。デフォルトでは、アプリの開始デスティネーションがトップレベル デスティネーションになります。
ユーザーがトップレベル デスティネーションにいるとき、デスティネーションで DrawerLayout
を使用している場合は、ナビゲーション ボタンがドロワー アイコン になります。デスティネーションで DrawerLayout
を使用していない場合は、ナビゲーション ボタンが非表示になります。ユーザーが他のデスティネーションにいるときは、ナビゲーション ボタンが「上へ」ボタン になります。開始デスティネーションのみをトップレベル ナビゲーションとして使用するナビゲーション ボタンを設定するには、対象のナビゲーション グラフを渡して AppBarConfiguration
オブジェクトを作成します。以下をご覧ください。
Kotlin
val appBarConfiguration = AppBarConfiguration(navController.graph)
Java
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(navController.getGraph()).build();
場合によっては、デフォルトの開始デスティネーションを使用する代わりに、複数のトップレベル デスティネーションの定義が必要となることもあります。このように、互いに階層構造としての関係がなく、それぞれが別々の関連デスティネーション群を持つような兄弟画面がある場合には、BottomNavigationView
を使用するのが一般的です。そのような場合は、コンストラクタにデスティネーション ID のセットを渡します。以下をご覧ください。
Kotlin
val appBarConfiguration = AppBarConfiguration(setOf(R.id.main, R.id.profile))
Java
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) }
Java
@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
に「上へ」ボタンを表示させる 2 つ目のアクティビティがある場合に便利です。それにより、バックスタックに他のデスティネーションがない場合、ユーザーは親アクティビティに戻ることができます。navigateUp()
による処理が他にない場合は、setFallbackOnNavigateUpListener()
を使用してフォールバック動作を制御できます。次の例をご覧ください。
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) }
Java
@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) }
Java
@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()
を呼び出します。以下をご覧ください。AppBarConfiguration
は、onSupportNavigateUp()
をオーバーライドする際にも使用するため、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) }
Java
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() }
Java
@Override public boolean onSupportNavigateUp() { NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment); return NavigationUI.navigateUp(navController, appBarConfiguration) || super.onSupportNavigateUp(); }
アプリバーの変化をサポートする
アプリ内のデスティネーション間でトップ アプリバーのレイアウトが大幅に変化しない場合は、アクティビティにトップ アプリバーを追加しても問題ありません。しかし、トップ アプリバーがデスティネーション間で大幅に変化する場合は、アクティビティからトップ アプリバーを削除し、各デスティネーション フラグメントでトップ アプリバーを定義することを検討してください。
たとえば、あるデスティネーションでは標準の Toolbar
を使用し、別のデスティネーションでは AppBarLayout
を使用してタブのある複雑なアプリバーを作成するとします(図 2 を参照)。
この例を NavigationUI
を使用してデスティネーション フラグメントに実装するには、まず各フラグメント レイアウトにアプリバーを定義します。1 つ目は、標準ツールバーを使用するデスティネーション フラグメントです。
<LinearLayout>
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
... />
...
</LinearLayout>
2 つ目は、タブ付きのアプリバーを使用するデスティネーション フラグメントです。
<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) }
Java
@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
には onNavDestinationSelected()
ヘルパー メソッドが含まれており、このヘルパー メソッドは、MenuItem
と、関連付けられたデスティネーションをホストする NavController
を受け取ります。MenuItem
の id
がデスティネーションの 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) }
Java
@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 パネルです。 ドロワーは、ユーザーがアプリバー内のドロワー アイコン()をタップしたときや、画面の左端からスワイプしたときに表示されます。
ドロワー アイコンは、DrawerLayout
を使用しているすべてのトップレベル デスティネーション上で表示されます。
ナビゲーション ドロワーを追加するには、まず、DrawerLayout
をルートビューとして宣言します。DrawerLayout
内に、メイン UI コンテンツのレイアウトと、ナビゲーション ドロワーのコンテンツを格納する別のビューを追加します。
たとえば、下記のレイアウトの場合、2 つの子ビューを持つ DrawerLayout
を使用しています。一方の子ビューは、メイン コンテンツを格納する NavHostFragment
で、もう一方の子ビューは、ナビゲーション ドロワーのコンテンツ用の NavigationView
です。
<?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>
次に、DrawerLayout
を AppBarConfiguration
に渡して、ナビゲーション グラフに接続します。次の例をご覧ください。
Kotlin
val appBarConfiguration = AppBarConfiguration(navController.graph, drawerLayout)
Java
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) }
Java
@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); }
Navigation 2.4.0-alpha01 以降では、setupWithNavController
を使用すると、各メニュー項目の状態を保存、復元できます。
ボトム ナビゲーション
NavigationUI
は、ボトム ナビゲーションも処理できます。ユーザーがメニュー項目を選択すると、NavController
は、onNavDestinationSelected()
を呼び出して、ボトム ナビゲーション バー内で選択されているアイテムを自動的に更新します。
アプリ内にボトム ナビゲーション バーを作成するには、まず、メイン アクティビティ内でバーを定義します。以下をご覧ください。
<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) }
Java
@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); }
Navigation 2.4.0-alpha01 以降では、setupWithNavController
を使用すると、各メニュー項目の状態を保存、復元できます。
ナビゲーション イベントをリッスンする
デスティネーション間のナビゲーションは、主に NavController
の操作によって実行されます。NavController
は、NavHost
のコンテンツを新しいデスティネーションに置き換える役割を担います。多くの場合、UI 要素(トップ アプリバーや、BottomNavigationBar
のような永続的ナビゲーション コントロールなど)は、NavHost
の外側に存在しており、デスティネーション間を移動する際に更新する必要があります。
NavController
は、NavController
の現在のデスティネーションまたはその引数が変更されたときに呼び出される OnDestinationChangedListener
インターフェースを提供します。新しいリスナーは、addOnDestinationChangedListener()
メソッドを通じて登録できます。addOnDestinationChangedListener()
を呼び出すときに、現在のデスティネーションが存在する場合は、すぐにリスナーに送信されます。
NavigationUI
は、OnDestinationChangedListener
を使用して、このような一般的な 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 } }
Java
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); } } });
引数ベースのリスナー
代わりの方法として、ナビゲーション グラフ内でデフォルト値を持つ引数を使用することもできます。この引数は、該当する UI コントローラが状態を更新するために使用できます。たとえば、上記の例のように OnDestinationChangedListener
内のロジックをデスティネーション ID に基づいて作成するのではなく、次のように NavGraph
内で引数を作成します。
<?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" android:id="@+id/navigation\_graph" app:startDestination="@id/fragmentOne"> <fragment android:id="@+id/fragmentOne" android:name="com.example.android.navigation.FragmentOne" android:label="FragmentOne"> <action android:id="@+id/action\_fragmentOne\_to\_fragmentTwo" app:destination="@id/fragmentTwo" /> </fragment> <fragment android:id="@+id/fragmentTwo" android:name="com.example.android.navigation.FragmentTwo" android:label="FragmentTwo"> <argument android:name="ShowAppBar" android:defaultValue="true" /> </fragment> </navigation>
この引数は、デスティネーションに移動するときに使用されるものではなく、defaultValue
を使用してデスティネーションに追加の情報を渡す方法として使用されます。この場合、この値は、デスティネーションにいるときにアプリバーを表示するかどうかを示しています。
これで、次のように OnDestinationChangedListener
を Activity
に追加できます。
Kotlin
navController.addOnDestinationChangedListener { _, _, arguments -> appBar.isVisible = arguments?.getBoolean("ShowAppBar", false) == true }
Java
navController.addOnDestinationChangedListener( new NavController.OnDestinationChangedListener() { @Override public void onDestinationChanged( @NonNull NavController controller, @NonNull NavDestination destination, @Nullable Bundle arguments ) { boolean showAppBar = false; if (arguments != null) { showAppBar = arguments.getBoolean("ShowAppBar", false); } if(showAppBar) { appBar.setVisibility(View.VISIBLE); } else { appBar.setVisibility(View.GONE); } } } );
NavController
は、ナビゲーション デスティネーションが変更されるたびにこのコールバックを呼び出します。Activity
は、コールバックで受け取った引数に基づいて、所有する UI コンポーネントの状態または可視性を更新できるようになりました。
このアプローチの利点として、Activity
はナビゲーション グラフの引数のみを参照し、個々の Fragment
の役割と責任については関知しないことが挙げられます。同様に、個々のフラグメントは、それに含まれる Activity
とその所有する UI コンポーネントについては関知しません。
参考情報
ナビゲーションについて詳しくは、以下の参考情報をご確認ください。
サンプル
Codelab
ブログ投稿
動画
- 単一アクティビティに移行する際の 10 のベスト プラクティス
- 単一アクティビティ: 移行する理由、タイミング、方法(Android Dev Summit 2018)
- Android Jetpack: ナビゲーション コントローラによる UI ナビゲーションの管理(Google I/O 2018)