AAC Navigation(v2.0) + setupWithNavControllerでUp buttonを制御する

公式ドキュメントはこちら。

developer.android.com

基本的な使い方

Navigation の導入時には NavigationUI 経由で NavControllerToolbarDrawerBottomNavigationView を組み合わせるとことになります。

developer.android.com

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/layout_toolbar"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.0">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:elevation="4dp" />
    </com.google.android.material.appbar.AppBarLayout>

    <fragment
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/layout_toolbar"
        app:navGraph="@navigation/nav_graph" />

</androidx.constraintlayout.widget.ConstraintLayout>
class RootActivity : AppCompatActivity {

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

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

これで Toolbarnav_graph に応じるようになります。

応用的な使い方

setupWithNavControllersetupActionBarWithNavController の第2引数 AppBarConfiguration に何も指定しなければ AppBarConfiguration(navController.graph) が入ります。 この結果 navController にセットされているナビゲーショングラフの startDestinationAppBarConfigurationmTopLevelDestinations に登録されます。

ref AppBarConfiguration.Builder

/**
 * Create a new Builder whose only top level destination is the start destination
 * of the given {@link NavGraph}. The Up button will not be displayed when on the
 * start destination of the graph.
 *
 * @param navGraph The NavGraph whose start destination should be considered the only
 *                 top level destination. The Up button will not be displayed when on the
 *                 start destination of the graph.
 */
public Builder(@NonNull NavGraph navGraph) {
    mTopLevelDestinations.add(NavigationUI.findStartDestination(navGraph).getId());
}

mTopLevelDestinations に登録されると、JavaDocの通りUp buttonが表示されなくなります。

ref AppBarConfiguration

/**
 * The set of destinations by id considered at the top level of your information hierarchy.
 * The Up button will not be displayed when on these destinations.
 *
 * @return The set of top level destinations by id.
 */
@NonNull
public Set<Integer> getTopLevelDestinations() {
    return mTopLevelDestinations;
}

このため mTopLevelDestinations に渡すidを調整することで、Up buttonを隠したり表示させたりすることができます。

If you want to customize which destinations are considered top-level destinations, you can instead pass a set of destination IDs to the constructor, as shown below:

ref Update UI components with NavigationUI  |  Android Developers

Up buttonを常に表示したい場合

idが含まれていないSetを用意します。

val appBarConfiguration = AppBarConfiguration.Builder(emptySet()).build()
findViewById<Toolbar>(R.id.toolbar).setupWithNavController(navController, appBarConfiguration)

この時 FallbackOnNavigateUpListenerが必要になっていないかご注意ください。 渡す場合には AppBarConfiguration.Builder(emptySet()).setFallbackOnNavigateUpListener("ココ").build() とすることになります。

/**
 * Adds a {@link OnNavigateUpListener} that will be called as a fallback if the default
 * behavior of {@link androidx.navigation.NavController#navigateUp}
 * returns <code>false</code>.
 *
 * @param fallbackOnNavigateUpListener Listener that will be invoked if
 *                                     {@link androidx.navigation.NavController#navigateUp}
 *                                     returns <code>false</code>.
 * @return this {@link Builder}
 */
@NonNull
public Builder setFallbackOnNavigateUpListener(
        @Nullable OnNavigateUpListener fallbackOnNavigateUpListener) {
    mFallbackOnNavigateUpListener = fallbackOnNavigateUpListener;
    return this;
}

例えば onBackPressed を追加することで、前のActivityに戻る処理なんかが考えられます。

Up buttonを常に非表示にしたい場合

ナビゲーショングラフの全てのidが含まれているSetを用意します。 NavController.iterator()イテレーターを返してくるので、minSDKVersionに合わせた実装になると思います。

val navController = findNavController(R.id.nav_host_fragment)
val idSet = mutableSetOf<Int>()
navController.graph.iterator().forEach { idSet.add(it.id) }
findViewById<Toolbar>(R.id.toolbar)
    .setupWithNavController(navController, AppBarConfiguration.Builder(idSet).build())

終わりに

色々とカスタマイズしたいケース、するケースが出ることで公式ライブラリ側で色々と修正が入るのではないかなーと思っています。 Navigation 2.0では今回のような方法を取ることになりますが、バージョンが上がればまた違う方法が出るかなと。

Navigationを入れるとFragmentがとんでもなく使いやすくなるので、これからもっと理解を深めていきたいと思います!