39
25

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Android】Navigationの遷移先にあわせてToolbarをカスタマイズする

Last updated at Posted at 2020-02-13

はじめに

みなさん、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を乗っけています。
styleNoActionBarにしています。

activity_main.xml
<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. タイトルを動的に切り替える

例えばリスト選択→詳細表示へ遷移した際に、選択した項目に合わせて動的にタイトルを設定したいという場合、以下の方法が使えます。

  1. NavGraphで設定する
  2. addOnDestinationChangedListener で設定する

準備

どちらの方法も準備は同じです。
NavGraphでタイトル用のargumentを設定します。

NavGraph
    <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のラベルに以下のように引数名を設定します。

NavGraph
    <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が見つからない」というエラーが発生します。

activity_main.xml
<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でタイトルを設定する

39
25
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
39
25

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?