はじめに
みなさん、Navigation Component使ってますか?
Fragment間の画面遷移や値の受け渡しなどを非常に楽にしてくれるNavigationですが、ToolBarと併せて使用した時にいくつか詰まったポイントがありましたので、本記事で紹介したいと思います。
※2020/02/14 FragmentContainerViewについて追記しました
この記事で紹介すること
- Navigation + Toolbarを扱う時に困りがちなことの解決方法
この記事で紹介しないこと
- Navigationの基本的な使い方
Navigationの使い方を知りたい場合はこちらの記事が参考になります。
リポジトリ
こちらに今回のサンプルを置いておきます。
https://github.com/nanaten/Navigation-and-Toolbar-Sample
前提
Activityのレイアウトはこんな感じです。
ActivityにToolbarとFragmentを乗っけています。
※style
はNoActionBar
にしています。
<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=".MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.appbar.AppBarLayout>
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/appbar"
app:navGraph="@navigation/main_nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
Tips
1. 特定のFragmentだけUpアイコンを表示させない
ログイン後のホーム画面など、この画面にはUpアイコン(←)を表示させたくない、という場合があると思います。
そういう時は AppBarConfigration
にUpアイコンを表示させたくないFragmentのIDを Set
で渡します。
val navController = findNavController(R.id.nav_host_fragment)
val appBarConfiguration = AppBarConfiguration(setOf(R.id.firstFragment))
setupActionBarWithNavController(navController, appBarConfiguration)
アイコンを表示させたくないFragmentが複数ある場合は、以下のように複数のIDを渡せます。
val appBarConfiguration = AppBarConfiguration(setOf(R.id.firstFragment, R.id.secondFragment))
2. 特定のFragmentだけToolbarを消したい
特定のFragmentに移動した時だけToolbarを表示させないようにしたい!と思ったことはありませんか?
Fragmentから操作しても良いのですが、なんとなくFragmentからActivityにあるコントロールを操作するのは気持ち悪い…
と思っていたら、navController.addOnNavigationDestinationListener
という便利なリスナーが存在しました。
val navController = findNavController(R.id.nav_host_fragment)
navController.addOnDestinationChangedListener { controller, destination, arguments -> }
例えば ThirdFragment
に移動した時だけToolbarを消したい、という場合は以下のように使えます。
val navController = findNavController(R.id.nav_host_fragment)
navController.addOnDestinationChangedListener { _, destination, _ ->
toolbar.visibility = if(destination.id == R.id.thirdFragment) View.GONE else View.VISIBLE
}
3. タイトルを動的に切り替える
例えばリスト選択→詳細表示へ遷移した際に、選択した項目に合わせて動的にタイトルを設定したいという場合、以下の方法が使えます。
- NavGraphで設定する
-
addOnDestinationChangedListener
で設定する
準備
どちらの方法も準備は同じです。
NavGraphでタイトル用のargumentを設定します。
<fragment
android:id="@+id/fourthFragment"
...>
<argument
android:name="title"
app:argType="string" />
</fragment>
SafeArgsでFragmentに引数を渡してあげます。
SafeArgsについてはこちらの記事が参考になります。
val title = "test title"
val args = FirstFragmentDirections.actionFirstToFourth(title)
findNavController().navigate(args)
1. NavGraphで設定する
NavGraphで設定する場合は、fragmentのラベルに以下のように引数名を設定します。
<fragment
android:id="@+id/fourthFragment"
...
android:label="{title}">
<argument
android:name="title"
app:argType="string" />
</fragment>
これだけで引数に渡した値がタイトルとして表示されるようになります!
2. addOnDestinationChangedListenerで設定する
こちらは先ほどの addOnDestinationChangedListener
を用いた方法になります。
3番目の引数 arguments
からFragmentへ渡したargumentが取得できるため、そちらを利用します。
navController.addOnDestinationChangedListener { _, _, arguments ->
if(arguments?.getString("title") != null) {
supportActionBar?.title = arguments.getString("title")
}
}
destination
でIDを判別しても良いですが、上記のようにargumentsの取得結果がnullかどうかで判別すれば、複数箇所で title
というargumentを使っている場合に対応が簡単になります。
Toolbarの設定をActivityで一括管理したい場合は addOnDestinationChangedListener
を使うのも一つの手かもしれません。
おまけ FragmentContainerViewを使う場合のfindNavControllerのやり方
自分でやってみて詰まったのでメモ代わりに追記しておきます。
FragmentContainerView
とは、今までFragmentのコンテナとして利用されていたFrameLayout
の代替Viewとして登場したものです。
詳しい解説はこちらの記事が参考になります。
レイアウトの fragment
を以下のように置き換えることが可能ですが、普通にonCreateで findNavController
すると「NavControllerが見つからない」というエラーが発生します。
<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=".MainActivity">
...
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/appbar"
app:navGraph="@navigation/main_nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val navController = findNavController(R.id.nav_host_fragment)
...
}
こちらにIssueがありますが、FragmentContainerView
に対して、onCreateの段階では fragmentManager
がINITIALIZING状態にあるために起きているそうです(たぶん…)
FragmentContainerView
を使う場合は、 navController
の取得方法を以下のように修正する必要があります。
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
FragmentContainerView
から直接ではなく、FragmentContainerView
にアタッチしたNavHostFragmentから取得しています(たぶん…)
おわりに
本記事を書くにあたって、以下のリンク先を参考にさせて頂きました。
・[stsnブログ] メモ Android: Navigation Component + Toolbar(ActionBar)周りのコードを読んで見る
・[Qiita] 特定のFragmentが表示される時にAppBarLayoutとBottomNavigationViewを非表示にする
・[Kenji Abe] Navigationでタイトルを設定する