Czy istnieje sposób na utrzymanie fragmentu przy życiu podczas korzystania z BottomNavigationView z nowym NavController?


101

Próbuję użyć nowego komponentu nawigacji. Używam BottomNavigationView z navController: NavigationUI.setupWithNavController (bottomNavigation, navController)

Ale kiedy zmieniam fragmenty, za każdym razem niszczą / tworzą, nawet jeśli były wcześniej używane.

Czy istnieje sposób, aby utrzymać przy życiu nasze główne fragmenty, które prowadzą do naszego widoku BottomNavigationView?


3
Zgodnie z komentarzem na issuetracker.google.com/issues/80029773 wydaje się, że problem zostanie ostatecznie rozwiązany. Byłbym również ciekawy, czy ludzie mają tanie obejście, które nie wymaga opuszczania biblioteki, aby tymczasowo to zadziałało.
Jimmy Alexander

Odpowiedzi:


72

Spróbuj tego.

Nawigator

Utwórz nawigator niestandardowy.

@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

Utwórz niestandardowy NavHostFragment.

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

navigation.xml

Użyj tagu niestandardowego zamiast tagu fragmentowego.

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

układ zajęć

Użyj CustomNavHostFragment zamiast NavHostFragment.

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

Aktualizacja

Stworzyłem przykładowy projekt. połączyć

Nie tworzę niestandardowego NavHostFragment. Używam navController.navigatorProvider += navigator.


3
Chociaż to rozwiązanie działa ( fragmentssą ponownie używane, a nie odtwarzane), występuje problem z nawigacją. Każdy menuitemz nich bottomnavigationviewjest uważany za podstawowy - dlatego po BACKnaciśnięciu aplikacja kończy pracę (lub przechodzi do górnego komponentu). Lepszym rozwiązaniem byłoby zachowywanie się tak, jak w aplikacji YouTube: (1) Dotknij ekranu głównego; (2) Dotknij Trendy; (3) Dotknij Skrzynka odbiorcza; (4) Dotknij Trendy; (5) Dotknij BACK- przechodzi do skrzynki odbiorczej; (6) Dotknij BACK- przechodzi do domu; (7) Tap BACK- aplikacja istnieje. Podsumowując, BACKfunkcja YouTube między „ menuitems” wraca do najnowszej, bez powtarzania elementów.
miguelt

3
Jak zachować funkcjonalność przycisku Wstecz? Aplikacja kończy pracę po naciśnięciu przycisku Wstecz.
Francis

5
@ jL4, miałem ten sam problem, prawdopodobnie zapomniałeś usunąć app:navGraphze swojej aktywności element NavHostFragment.
Paul Chernenko,

7
Dlaczego Google nie oferuje tej funkcji po wyjęciu z pudełka?
Arsenius

61
NavigationComponent powinien stać się rozwiązaniem, a nie kolejnym problemem, więc programista musi stworzyć takie obejście.
Arie Agung

24

Link do próbek Google Po prostu skopiuj NavigationExtensions do swojej aplikacji i skonfiguruj według przykładu. Działa świetnie.


1
Ale działa tylko w przypadku nawigacji dolnej. Jeśli masz ogólną nawigację, masz problem
Georgiy Chebotarev

Mam ten sam problem i każde rozwiązanie, które sprawdzam, zawiera kod kotlin, ale obawiam się, że w moim projekcie używam Javy. Czy ktoś może mi pomóc, jak rozwiązać ten problem w java.
Innowacje CodeRED

@CodeREDInnovations ja też, chociaż próbowałem i pomyślnie skompilowałem z plikiem kotlin, nawigacja pozostaje taka: imgur.com/a/DcsKqPr nie "zastępuje", poza tym wydaje się, że aplikacja stała się cięższa i zacina się.
Acauã Pitta

@ AcauãPitta Czy znalazłeś rozwiązanie w Javie?
developKinberg

@CodeREDInnovations Możemy używać funkcji rozszerzających z kotlin w pliku java. Aby to zrobić, musisz skonfigurować kotlin w gradle, wygląda na to, że jest to najbardziej optymalny sposób.
Zachar Rodionow

11

Po wielu godzinach poszukiwań znalazłem rozwiązanie. Cały czas było tuż przed nami :) Jest funkcja: popBackStack(destination, inclusive)która nawiguje do podanego celu, jeśli znajdzie się w backStack. Zwraca wartość logiczną, więc możemy nawigować tam ręcznie, jeśli kontroler nie znajdzie fragmentu.

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)
    }

2
Jak i gdzie tego używać? Jeśli przejdę z fragmentu A do B, a następnie z B do A, jak mogę przejść bez wywoływania metod oncraeteView () i onViewCreated (), w skrócie bez ponownego uruchamiania fragmentu A
Kishan Solanki

@UsamaSaeedUS: Czy możesz udostępnić kod, jak go używać? Jak to, o czym wspomniał Kishan.
Code_Life

2

Jeśli masz problemy z przekazywaniem argumentów, dodaj:

fragment.arguments = args

w klasie KeepStateNavigator


1

Obecnie niedostępne.

Aby obejść ten problem, możesz przechowywać wszystkie pobrane dane w modelu widoku i mieć te dane łatwo dostępne podczas ponownego tworzenia fragmentu. Upewnij się, że otrzymujesz widok, używając kontekstu działań.

Możesz użyć LiveData, aby obserwować cykl życia danych


2
To prawda, a data> view, ale problem występuje, gdy chcesz również zachować stan widoku, np. Załadowano wiele stron, a użytkownik przewinął w dół :).
Joaquim Ley

1

Skorzystałem z linku dostarczonego przez @STAR_ZERO i działa dobrze. Dla tych, którzy mają problem z przyciskiem Wstecz, możesz sobie z tym poradzić w hoście aktywności / nawigacji w ten sposób.

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

Po prostu sprawdź, czy aktualnym miejscem docelowym jest Twój fragment główny / domowy (zwykle pierwszy w dolnym widoku nawigacji), jeśli nie, po prostu przejdź z powrotem do fragmentu, jeśli tak, wyjdź z aplikacji lub zrób, co chcesz.

Przy okazji, to rozwiązanie musi współpracować z powyższym linkiem do rozwiązania dostarczonym przez STAR_ZERO, używając keep_state_fragment .


1
idk why but currentDestination !!. id zawsze podaje mi te same wartości dla wszystkich 3 fragmentów
Avishek Das

1

Rozwiązanie dostarczone przez @ piotr-prus pomogło mi, ale musiałem dodać trochę aktualnego sprawdzenia miejsca docelowego:

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

bez tego sprawdzenia aktualny cel zostanie odtworzony, jeśli przez pomyłkę do niego przejdziesz, ponieważ nie zostałby znaleziony na tylnym stosie.


Cieszę się, że moje rozwiązanie zadziałało. Właściwie sprawdzam bieżące menuItem w bottomNavigationView przed wywołaniem fragmentu z backStack za pomocą: bottomNavigationView.setOnNavigationItemSelectedListener
Piotr Prus

-1

Zgodnie z Androidem Dokumentacji ,

Podczas tworzenia NavHostFragment przy użyciu FragmentContainerView lub w przypadku ręcznego dodawania NavHostFragment do działania za pośrednictwem FragmentTransaction, próba pobrania NavController w onCreate () działania za pośrednictwem Navigation.findNavController (Activity, @IdRes int) nie powiedzie się. Zamiast tego należy pobrać NavController bezpośrednio z NavHostFragment.

Zmień nav_host_fragment na -> FragmentContainerView i pobierz navController

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

Korzystając z tego podejścia, można zachować stan fragmentu za pomocą składników nawigacji

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.