Apakah ada cara untuk menjaga fragmen tetap hidup saat menggunakan BottomNavigationView dengan NavController baru?


96

Saya mencoba menggunakan komponen navigasi baru. Saya menggunakan BottomNavigationView dengan navController: NavigationUI.setupWithNavController (bottomNavigation, navController)

Tetapi ketika saya mengganti fragmen, mereka setiap kali menghancurkan / membuat bahkan jika mereka sebelumnya digunakan.

Apakah ada cara untuk tetap menghidupkan tautan fragmen utama kami ke BottomNavigationView?


2
Menurut komentar di Issuetracker.google.com/issues/80029773 , tampaknya ini akan diselesaikan pada akhirnya. Saya juga akan penasaran namun jika orang memiliki solusi murah yang tidak melibatkan meninggalkan perpustakaan untuk membuat ini berfungsi untuk sementara.
Jimmy Alexander

Jawaban:


69

Coba ini.

Navigator

Buat navigator khusus.

@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

Buat NavHostFragment kustom.

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

navigation.xml

Gunakan tag kustom, bukan tag fragmen.

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

tata letak aktivitas

Gunakan CustomNavHostFragment sebagai ganti 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>

Memperbarui

Saya membuat proyek sampel. tautan

Saya tidak membuat NavHostFragment kustom. Saya menggunakan navController.navigatorProvider += navigator.


3
Meskipun solusi ini berfungsi ( fragmentsdigunakan kembali, tidak dibuat ulang), ada masalah dengan navigasi. Masing-masing menuitemin bottomnavigationviewdianggap utama - jadi, saat BACKditekan, aplikasi selesai (atau beralih ke komponen atas). Solusi yang lebih baik adalah berperilaku seperti aplikasi YouTube: (1) Ketuk Beranda; (2) Ketuk Trending; (3) Ketuk Kotak Masuk; (4) Ketuk Trending; (5) Ketuk BACK- pergi ke Kotak Masuk; (6) Ketuk BACK- pergi ke Rumah; (7) Ketuk BACK- aplikasi ada. Singkatnya, BACKfungsi YouTube antara " menuitems" kembali ke yang terbaru, tanpa mengulang item.
miguelt

3
Bagaimana cara mempertahankan fungsionalitas tombol kembali? Aplikasi selesai saat tombol kembali ditekan.
Francis

5
@ jL4, Saya mengalami masalah yang sama, mungkin Anda lupa menghapus app:navGraphNavHostFragment aktivitas Anda.
Paul Chernenko

6
Mengapa Google tidak mengabaikan fungsi ini di luar kotak?
Arsenius

55
NavigationComponent harus menjadi solusi bukan menjadi masalah lain sehingga programmer harus membuat solusi seperti ini.
Arie Agung

24

Tautan sampel Google Cukup salin NavigationExtensions ke aplikasi Anda dan konfigurasikan dengan contoh. Bekerja dengan baik.


1
Tetapi itu hanya berfungsi untuk navigasi bawah. Jika Anda memiliki navigasi umum, Anda mengalami masalah
Georgiy Chebotarev

Saya menghadapi masalah yang sama dan setiap solusi yang saya periksa berisi kode kotlin tetapi saya khawatir saya menggunakan Java dalam proyek saya. Adakah yang bisa membantu saya cara memperbaiki masalah ini di java.
Inovasi CodeRED

@CodeREDInnovations saya juga, meskipun saya sudah mencoba dan berhasil dikompilasi dengan file kotlin navigasi tetap seperti ini: imgur.com/a/DcsKqPr tidak "menggantikan", selain itu, tampaknya aplikasi menjadi lebih berat dan macet.
Acauã Pitta

@ AcauãPitta apakah Anda menemukan solusi di Java?
developKinberg

@developKinberg no brother ..
Acauã Pitta

11

Setelah berjam-jam penelitian saya menemukan solusi. Itu semua waktu tepat di depan kami :) Ada fungsi: popBackStack(destination, inclusive)yang menavigasi ke tujuan tertentu jika ditemukan di backStack. Ini mengembalikan Boolean, jadi kita bisa menavigasi ke sana secara manual jika pengontrol tidak akan menemukan fragmen.

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

Bagaimana dan di mana menggunakan ini? Jika saya menavigasi dari Fragmen A ke B, lalu Dari B ke A bagaimana saya bisa pergi tanpa memanggil metode oncraeteView () dan onViewCreated (), singkatnya tanpa memulai ulang fragmen A
Kishan Solanki

@UsamaSaeedUS: Bisakah Anda membagikan kode, bagaimana cara menggunakannya? Seperti yang disebutkan oleh Kishan.
Code_Life

2

Jika Anda kesulitan menyampaikan argumen, tambahkan:

fragment.arguments = args

di kelas KeepStateNavigator


1

Tidak tersedia untuk sekarang.

Sebagai solusinya, Anda dapat menyimpan semua data yang diambil ke dalam model tampilan dan menyediakan data tersebut saat Anda membuat ulang fragmen. Pastikan Anda mendapatkan tampilan menggunakan konteks aktivitas.

Anda dapat menggunakan LiveData untuk membuat data Anda sadar siklus proses


1
Ini memang benar, dan data> view tetapi masalahnya adalah ketika Anda ingin juga mempertahankan status tampilan, misalnya beberapa halaman dimuat dan pengguna telah menggulir ke bawah :).
Joaquim Ley

1

Saya telah menggunakan tautan yang disediakan oleh @STAR_ZERO dan berfungsi dengan baik. Bagi yang bermasalah dengan tombol kembali, Anda bisa mengatasinya di aktivitas / nav host seperti ini.

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

Cukup periksa apakah tujuan saat ini adalah fragmen root / home Anda (biasanya yang pertama di tampilan navigasi bawah), jika tidak, cukup navigasikan kembali ke fragmen tersebut, jika ya, hanya keluar dari aplikasi atau lakukan apa pun yang Anda inginkan.

Btw, solusi ini perlu bekerja sama dengan tautan solusi di atas yang disediakan oleh STAR_ZERO, menggunakan keep_state_fragment .


saya tidak tahu mengapa tapi currentDestination !!. id selalu memberi saya nilai yang sama untuk semua 3 fragmen
Avishek Das

1

Solusi yang diberikan oleh @ piotr-prus membantu saya, tetapi saya harus menambahkan beberapa pemeriksaan tujuan saat ini:

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

tanpa pemeriksaan ini, tujuan saat ini akan dibuat ulang jika Anda salah menavigasi ke sana, karena tidak akan ditemukan di back-stack.


Saya senang solusi saya berhasil untuk Anda. Saya sebenarnya memeriksa menuItem saat ini di bottomNavigationView sebelum memanggil fragmen dari backStack menggunakan: bottomNavigationView.setOnNavigationItemSelectedListener
Piotr Prus

0

Menurut Dokumentasi Android ,

Saat membuat NavHostFragment menggunakan FragmentContainerView atau jika secara manual menambahkan NavHostFragment ke aktivitas Anda melalui FragmentTransaction, upaya untuk mengambil NavController di onCreate () dari suatu Aktivitas melalui Navigation.findNavController (Activity, @IdRes int) akan gagal. Anda harus mengambil NavController langsung dari NavHostFragment sebagai gantinya.

Ubah nav_host_fragment Anda menjadi -> FragmentContainerView dan dalam mengambil navController

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

Anda dapat mempertahankan status fragmen dengan Komponen Navigasi menggunakan pendekatan ini

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.