新しいNavControllerでBottomNavigationViewを使用するときに、フラグメントを存続させる方法はありますか?


95

新しいナビゲーションコンポーネントを使用しようとしています。navControllerでBottomNavigationViewを使用します:NavigationUI.setupWithNavController(bottomNavigation、navController)

しかし、フラグメントを切り替えると、以前に使用されていたとしても、毎回破棄/作成されます。

BottomNavigationViewへのメインフラグメントリンクを存続させる方法はありますか?


2
issuetracker.google.com/issues/80029773のコメントによると、これは最終的に解決されるようです。しかし、暫定的にこの作業を行うためにライブラリを放棄することを伴わない安価な回避策があれば、私も興味があります。
ジミーアレクサンダー

回答:


68

これを試して。

ナビゲーター

カスタムナビゲーターを作成します。

@Navigator.Name("custom_fragment")  // Use as custom tag at navigation.xml
class CustomNavigator(
    private val context: Context,
    private val manager: FragmentManager,
    private val containerId: Int
) : FragmentNavigator(context, manager, containerId) {

    override fun navigate(destination: Destination, args: Bundle?, navOptions: NavOptions?) {
        val tag = destination.id.toString()
        val transaction = manager.beginTransaction()

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

        var fragment = manager.findFragmentByTag(tag)
        if (fragment == null) {
            fragment = destination.createFragment(args)
            transaction.add(containerId, fragment, tag)
        } else {
            transaction.attach(fragment)
        }

        transaction.setPrimaryNavigationFragment(fragment)
        transaction.setReorderingAllowed(true)
        transaction.commit()

        dispatchOnNavigatorNavigated(destination.id, BACK_STACK_DESTINATION_ADDED)
    }
}

NavHostFragment

カスタムNavHostFragmentを作成します。

class CustomNavHostFragment: NavHostFragment() {
    override fun onCreateNavController(navController: NavController) {
        super.onCreateNavController(navController)
        navController.navigatorProvider += PersistentNavigator(context!!, childFragmentManager, id)
    }
}

Navigation.xml

フラグメントタグの代わりにカスタムタグを使用します。

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/navigation"
    app:startDestination="@id/navigation_first">

    <custom_fragment
        android:id="@+id/navigation_first"
        android:name="com.example.sample.FirstFragment"
        android:label="FirstFragment" />
    <custom_fragment
        android:id="@+id/navigation_second"
        android:name="com.example.sample.SecondFragment"
        android:label="SecondFragment" />
</navigation>

アクティビティレイアウト

NavHostFragmentの代わりにCustomNavHostFragmentを使用します。

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:id="@+id/nav_host_fragment"
        android:name="com.example.sample.CustomNavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@+id/bottom_navigation"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/navigation" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottom_navigation"
        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:menu="@menu/navigation" />

</androidx.constraintlayout.widget.ConstraintLayout>

更新

サンプルプロジェクトを作成しました。リンク

カスタムNavHostFragmentは作成しません。私は使用しますnavController.navigatorProvider += navigator


3
このソリューションは機能しますが(fragments再作成ではなく再利用されます)、ナビゲーションに問題があります。それぞれmenuitemには、bottomnavigationview一次と見なされる-このように、BACK押圧され、アプリの終了(又は上部コンポーネントになります)。より良い解決策は、YouTubeアプリと同じように動作することです。(1)[ホーム]をタップします。(2)[トレンド]をタップします。(3)[受信トレイ]をタップします。(4)[トレンド]をタップします。(5)タップBACK-受信トレイに移動します。(6)タップBACK-ホームに移動します。(7)タップBACK-アプリが存在します。要約すると、BACKmenuitems」の間のYouTube機能は、アイテムを繰り返さずに最新のものに戻ります。
miguelt 2018年

3
戻るボタンの機能を維持する方法は?戻るボタンを押すとアプリが終了します。
フランシス

5
@ jL4、同じ問題が発生しました。おそらくapp:navGraph、アクティビティのNavHostFragmentから削除するのを忘れていました。
ポールチェルネンコ2018

6
なぜグーグルはこの機能を箱から出して羊を飼わないのですか?
アルセニウス

55
NavigationComponentは別の問題になるのではなく解決策になるはずなので、プログラマーはこのような回避策を作成する必要があります。
ArieAgung19年

24

Googleのサンプルリンク NavigationExtensionsをアプリケーションにコピーし、例を使用して構成するだけです。よく働く。


1
ただし、下部ナビゲーションでのみ機能します。一般的なナビゲーションがある場合は、問題があります
Georgiy Chebotarev

私は同じ問題に直面しており、チェックするすべてのソリューションにkotlinコードが含まれていますが、プロジェクトでJavaを使用しているのではないかと心配しています。誰かがJavaでこの問題を修正する方法を教えてもらえますか?
イノベーションをCodeRedの

@CodeREDInnovations私も、kotlinファイルを使用して正常にコンパイルしようとしましたが、ナビゲーションは次のようになります:imgur.com/a/DcsKqPrそれ以外は、アプリが重くなり、スタックしているようです。
Acauãピッタ

11

何時間も研究した後、私は解決策を見つけました。それはいつも私たちの目の前にありました:)popBackStack(destination, inclusive)backStackで見つかった場合に指定された宛先にナビゲートする機能があります。ブール値を返すため、コントローラーがフラグメントを見つけられない場合は、手動でナビゲートできます。

if(findNavController().popBackStack(R.id.settingsFragment, false)) {
        Log.d(TAG, "SettingsFragment found in backStack")
    } else {
        Log.d(TAG, "SettingsFragment not found in backStack, navigate manually")
        findNavController().navigate(R.id.settingsFragment)
    }

これをどこでどのように使用しますか?フラグメントAからBに移動し、次にBからAに移動する場合、oncraeteView()メソッドとonViewCreated()メソッドを呼び出さずに、つまりフラグメントAを再起動せずに移動するにはどうすればよいですか
Kishan Solanki

@UsamaSaeedUS:コードとその使用方法を教えていただけますか?Kishanが言っているように。
code_Life

2

引数の受け渡しに問題がある場合は、以下を追加してください。

fragment.arguments = args

クラスで KeepStateNavigator


1

現在ご利用いただけません。

回避策として、フェッチしたすべてのデータをビューモデルに保存し、フラグメントを再作成するときにそのデータをすぐに利用できるようにすることができます。アクティビティコンテキストを使用してビューを取得するようにしてください。

LiveDataを使用して、データのライフサイクルを認識できるようにすることができます


1
これは確かに真実であり、データ>ビューですが、問題は、ビューの状態も保持したい場合です。たとえば、複数のページが読み込まれ、ユーザーが下にスクロールした場合などです:)。
JoaquimLey19年

1

@STAR_ZEROが提供するリンクを使用しましたが、正常に機能します。戻るボタンに問題がある場合は、このようにアクティビティ/ナビゲーションホストで処理できます。

override fun onBackPressed() {
        if(navController.currentDestination!!.id!=R.id.homeFragment){
            navController.navigate(R.id.homeFragment)
        }else{
            super.onBackPressed()
        }
    }

現在の宛先がルート/ホームフラグメント(通常は下部のナビゲーションビューの最初のフラグメント)であるかどうかを確認します。そうでない場合は、フラグメントに戻ります。そうである場合は、アプリを終了するか、必要な操作を行います。

ところで、このソリューションは、keep_state_fragmentを使用して、STAR_ZEROによって提供される上記のソリューションリンクと連携する必要があります。


idk why but currentDestination !!。idは、3つのフラグメントすべてに常に同じ値を与えます
Avishek Das

1

@ piotr-prusによって提供された解決策は私を助けました、しかし私はいくつかの現在の目的地チェックを追加しなければなりませんでした:

if (navController.currentDestination?.id == resId) {
    return       //do not navigate
}

このチェックを行わないと、現在の宛先が誤ってナビゲートされた場合に再作成されます。これは、バックスタックで見つからないためです。


私のソリューションがあなたのために働いてくれてうれしいです。私は実際にbottomNavigationViewの現在のmenuItemをチェックしてから、以下を使用してbackStackからフラグメントを呼び出しています:bottomNavigationView.setOnNavigationItemSelectedListener
Piotr Prus

0

Androidのドキュメントによると、

FragmentContainerViewを使用してNavHostFragmentを作成する場合、またはFragmentTransactionを介してNavHostFragmentをアクティビティに手動で追加する場合、Navigation.findNavController(Activity、@ IdRes int)を介してアクティビティのonCreate()でNavControllerを取得しようとすると失敗します。代わりに、NavControllerFragmentから直接NavControllerを取得する必要があります。

> -にあなたのnav_host_fragmentを変更FragmentContainerView とretriveからNavControllerに

    val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    val navController = navHostFragment.navController

このアプローチを使用して、ナビゲーションコンポーネントでフラグメントの状態を保持できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.