LoginSignup
43
25

More than 3 years have passed since last update.

Android BottomNavigationView と Navigation併用時の状態保持

Last updated at Posted at 2019-08-22

はじめに

AndroidでBottomNavigationViewとNavigationを併用する場合、デフォルトでタブ切り替え時にFragmentが再生成されます。
よって、再生成されないようにするためにはFragmentの状態保持を実装する必要があります。

この実装方法について調べた際、最新バージョンのNavigationを使った実装方法にたどり着けなく、苦労したのでまとめます。

言語はKotlinでバージョンは1.3.31です。

導入

app/build.gradle
// ...

dependencies {

    // ...

    // Navigation
    def arch_navigation_version = '2.2.0-alpha01'
    implementation "androidx.navigation:navigation-fragment:$arch_navigation_version"
    implementation "androidx.navigation:navigation-fragment-ktx:$arch_navigation_version"
    implementation "androidx.navigation:navigation-ui:$arch_navigation_version"
    implementation "androidx.navigation:navigation-ui-ktx:$arch_navigation_version"

    // Material
    implementation 'com.google.android.material:material:1.0.0'
}

FragmentNavigatorクラスのnavigate()を上書き

Navigationライブラリに実装されているFragmentNavigatorクラスのnavigate()でタブ切り替え時にFragmentが再生成されるようになっています。
よって、FragmentNavigatorクラスを継承したCustomNavigatorクラスを作成し、状態保持されるようにnavigate()を上書きます。

アニメーション周りの実装とBackStack周りの実装を排除し、状態保持したタブ切り替えのみを行うnavigate()が以下です。

CustomNavigator.kt
@Navigator.Name("custom_fragment")
class CustomNavigator(
    private val context: Context,
    private val manager: FragmentManager,
    private val containerId: Int
) : FragmentNavigator(context, manager, containerId) {

    @Suppress("DEPRECATION")
    override fun navigate(
        destination: Destination,
        args: Bundle?,
        navOptions: NavOptions?,
        navigatorExtras: Navigator.Extras?
    ): NavDestination? {
        if (manager.isStateSaved) {
            return null
        }

        var className = destination.className
        if (className[0] == '.') {
            className = context.packageName + className
        }

        val tag = destination.id.toString()
        val transaction = manager.beginTransaction()

        val currentFragment = manager.primaryNavigationFragment
        if (currentFragment != null) {
            transaction.hide(currentFragment)
        }

        var fragment = manager.findFragmentByTag(tag)
        if (fragment == null) {
            fragment = instantiateFragment(context, manager, className, args)
            transaction.add(containerId, fragment, tag)
        }
        fragment.arguments = args

        transaction.show(fragment)
        transaction.setPrimaryNavigationFragment(fragment)
        transaction.commit()

        return destination
    }
}

NavHostFragmentクラスのcreateFragmentNavigator()を上書き

次に、Navigationライブラリに実装されているNavHostFragmentクラスのcreateFragmentNavigator()を上書きます。FragmentNavigatorクラスのインスタンスが戻り値になっているためです。

NavHostFragmentを継承したCustomNavHostFragmentを作成し、上記のCustomNavigatorクラスのインスタンスが戻り値になるようにcreateFragmentNavigator()を上書きます。

CustomNavHostFragment.kt
class CustomNavHostFragment : NavHostFragment() {
    override fun createFragmentNavigator(): Navigator<out FragmentNavigator.Destination> {
        return CustomNavigator(requireContext(), childFragmentManager, id)
    }
}

navigationレイアウトの作成

CustomNavigatorクラスにつけた@Navigator.Nameアノテーションの引数に指定しているcustom_fragmentを利用します。

navigation.kt
<?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/bottom_navigation"
    app:startDestination="@+id/tab1">

    <custom_fragment
        android:id="@+id/tab1"
        android:name="hoge.HogeFragment"
        android:label="tab1"
        android:tag="tab1" />

    <custom_fragment
        android:id="@+id/tab2"
        android:name="hoge.HogeHogeFragment"
        android:label="tab2"
        android:tag="tab2" />

</navigation>

fragmentとBottomNavigationViewをレイアウトに作成

BottomNavigationViewとFragmentを表示したいレイアウトにBottomNavigationViewfragmentを記載します。

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout 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">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="hoge.MainActivity">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">
        </androidx.appcompat.widget.Toolbar>

        <fragment
            android:id="@+id/bottom_navigation_view_fragment"
            android:name="hoge.navigation.CustomNavHostFragment"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:defaultNavHost="true"
            app:layout_constraintBottom_toTopOf="@id/bottom_navigation_view"
            app:layout_constraintTop_toBottomOf="@+id/toolbar"
            app:navGraph="@navigation/navigation" />

        <com.google.android.material.bottomnavigation.BottomNavigationView
            android:id="@+id/bottom_navigation_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="?android:attr/windowBackground"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:menu="@menu/bottom_navigation_menu" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

以下が注意点です

  • fragment
    • android:nameCustomNavHostFragmentを指定
    • app:navGraphに上記で記載したnavigationレイアウトを指定
  • BottomNavigationView
    • app:menuに指定するmenuレイアウトのitemandroid:idcustom_fragmentandroid:idと揃えること
43
25
2

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
43
25