Android Jetpack Navigation, BottomNavigationView with Youtube or Instagram like proper back navigation (fragment back stack)?

前端 未结 13 1979
误落风尘
误落风尘 2020-12-12 10:25

Android Jetpack Navigation, BottomNavigationView with auto fragment back stack on back button click?

What I wanted, after choosing multiple tabs one after another by

相关标签:
13条回答
  • 2020-12-12 11:23

    You don't really need a ViewPager to work with BottomNavigation and the new Navigation architecture component. I have been working in a sample app that uses exactly the two, see here.

    The basic concept is this, you have the main activity that will host the BottomNavigationView and that is the Navigation host for your navigation graph, this is how the xml for it look like:

    activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout 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"
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".main.MainActivity">
    
        <fragment
            android:id="@+id/my_nav_host_fragment"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            app:defaultNavHost="true"
            app:layout_constraintBottom_toTopOf="@+id/navigation"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:navGraph="@navigation/nav_graph" />
    
        <android.support.design.widget.BottomNavigationView
            android:id="@+id/navigation"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="0dp"
            android:layout_marginEnd="0dp"
            android:background="?android:attr/windowBackground"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:menu="@menu/navigation" />
    
    </android.support.constraint.ConstraintLayout>
    

    The navigation Menu (tabs menu) for the BottomNavigationView looks like this:

    navigation.xml

    <?xml version="1.0" encoding="utf-8"?>
    <menu xmlns:android="http://schemas.android.com/apk/res/android">
    
        <item
            android:id="@+id/navigation_home"
            android:icon="@drawable/ic_home"
            android:title="@string/title_home" />
    
        <item
            android:id="@+id/navigation_people"
            android:icon="@drawable/ic_group"
            android:title="@string/title_people" />
    
        <item
            android:id="@+id/navigation_organization"
            android:icon="@drawable/ic_organization"
            android:title="@string/title_organization" />
    
        <item
            android:id="@+id/navigation_business"
            android:icon="@drawable/ic_business"
            android:title="@string/title_business" />
    
        <item
            android:id="@+id/navigation_tasks"
            android:icon="@drawable/ic_dashboard"
            android:title="@string/title_tasks" />
    
    </menu>
    

    All of this is just the BottomNavigationView setup. Now to make it work with the Navigation Arch Component you need to go into the navigation graph editor, add all your fragment destinations (in my case I have 5 of them, one for each tab) and set the id of the destination with the same name as the one in the navigation.xml file:

    This will tell android to make a link between the tab and the fragment, now every time the user clicks the "Home" tab android will take care of loading up the correct fragment. There is also one piece of kotlin code that needs to be added to your NavHost (the main activity) to wire things up with the BottomNavigationView:

    You need to add in your onCreate:

    bottomNavigation.setupWithNavController(Navigation.findNavController(this, R.id.my_nav_host_fragment))
    

    This tells android to do the wiring between the Navigation architecture component and the BottomNavigationView. See more in the docs.

    To get the same beahvior you have when you use youtube, just add this:

    navigation.setOnNavigationItemSelectedListener {item ->
    
                onNavDestinationSelected(item, Navigation.findNavController(this, R.id.my_nav_host_fragment))
    
            }
    

    This will make destinations go into the backstack so when you hit the back button, the last visited destination will be popped up.

    0 讨论(0)
  • 2020-12-12 11:24

    If you have a bottomNavigationView with 3 items corresponding to 3 Fragments: FragmentA, FragmentB and FragmentC where FragmentA is the startDestination in your navigation graph, then when you're on FragmentB or FragmentC and you click back, you're going to be redirected to FragmentA, that's the behavior recommended by Google and that's implemented by default.

    If however you wish to alter this behavior, you'll need to either use a ViewPager as was suggested by some of the other answers, or manually handle the fragments backStack and back transactions yourself -which in a way would undermine the use of the Navigation component altogether-.

    0 讨论(0)
  • 2020-12-12 11:25

    I didn't find any official solutions, but I use my own way

    First, I create Stack for handle fragments

        needToAddToBackStack : Boolen = true
    
    
        private lateinit var fragmentBackStack: Stack<Int>
        fragmentBackStack = Stack()
    

    and in

    navController.addOnDestinationChangedListener { _, destination, _ ->
            if (needToAddToBackStack) {
                fragmentBackStack.add(destination.id)
            }
    
    
            needToAddToBackStack = true
    
        }
    

    and handle the back button

    override fun onBackPressed() {
        if (::fragmentBackStack.isInitialized && fragmentBackStack.size > 1) {
            fragmentBackStack.pop()
            val fragmentId = fragmentBackStack.lastElement()
            needToAddToBackStack = false
            navController.navigate(fragmentId)
    
        } else {
            if (::fragmentBackStack.isInitialized && fragmentBackStack.size == 1) {
                finish()
            } else {
                super.onBackPressed()
            }
        }
    
    0 讨论(0)
  • 2020-12-12 11:27

    You have to set host navigation like below xml:

    <LinearLayout 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:orientation="vertical">
    
        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/colorPrimary" />
    
        <fragment
            android:id="@+id/navigation_host_fragment"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            app:defaultNavHost="true"
            app:navGraph="@navigation/nav_graph" />
    
        <android.support.design.widget.BottomNavigationView
            android:id="@+id/bottom_navigation_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:itemIconTint="@drawable/color_state_list"
            app:itemTextColor="@drawable/color_state_list"
            app:menu="@menu/menu_bottom_navigation" />
    </LinearLayout>
    

    Setup With Navigation Controller :

    NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.navigation_host_fragment);
    NavigationUI.setupWithNavController(bottomNavigationView, navHostFragment.getNavController());
    

    menu_bottom_navigation.xml :

    <?xml version="1.0" encoding="utf-8"?>
    <menu xmlns:android="http://schemas.android.com/apk/res/android">
        <item
            android:id="@id/tab1"  // Id of navigation graph 
            android:icon="@mipmap/ic_launcher"
            android:title="@string/tab1" />
        <item
            android:id="@id/tab2" // Id of navigation graph
            android:icon="@mipmap/ic_launcher"
            android:title="@string/tab2" />
    
        <item
            android:id="@id/tab3" // Id of navigation graph
            android:icon="@mipmap/ic_launcher"
            android:title="@string/tab3" />
    </menu>
    

    nav_graph.xml :

    <navigation 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"
        android:id="@+id/nav_graph"
        app:startDestination="@id/tab1">
        <fragment
            android:id="@+id/tab1"
            android:name="com.navigationsample.Tab1Fragment"
            android:label="@string/tab1"
            tools:layout="@layout/fragment_tab_1" />
    
        <fragment
            android:id="@+id/tab2"
            android:name="com.navigationsample.Tab2Fragment"
            android:label="@string/tab2"
            tools:layout="@layout/fragment_tab_2"/>
    
        <fragment
            android:id="@+id/tab3"
            android:name="com.simform.navigationsample.Tab3Fragment"
            android:label="@string/tab3"
            tools:layout="@layout/fragment_tab_3"/>
    </navigation>
    

    By setting up the same id of "nav_graph" to "menu_bottom_navigation" will handle the click of bottom navigation.

    You can handle back action using popUpTo property in action tag.

    0 讨论(0)
  • 2020-12-12 11:27

    First, let me clarify how Youtube and Instagram handles fragment navigation.

    • When the user is on a detail fragment, back or up pop the stack once, with the state properly restaured. A second click on the already selected bottom bar item pop all the stack to the root, refreshing it
    • When the user is on a root fragment, back goes to the last menu selected on the bottom bar, displaying the last detail fragment, with the state properly restaured (JetPack doesn't)
    • When the user is on the start destination fragment, back finishes activity

    None of the other answers above solve all this problems using the jetpack navigation.

    JetPack navigation has no standard way to do this, the way that I found more simple is to dividing the navigation xml graph into one for each bottom navigation item, handling the back stack between the navigation items myself using the activity FragmentManager and use the JetPack NavController to handle the internal navigation between root and detail fragments (its implementation uses the childFragmentManager stack).

    Suppose you have in your navigation folder this 3 xmls:

    res/navigation/
        navigation_feed.xml
        navigation_explore.xml
        navigation_profile.xml
    

    Have your destinationIds inside the navigation xmls the same of your bottomNavigationBar menu ids. Also, to each xml set the app:startDestination to the fragment that you want as the root of the navigation item.

    Create a class BottomNavController.kt:

    class BottomNavController(
            val context: Context,
            @IdRes val containerId: Int,
            @IdRes val appStartDestinationId: Int
    ) {
        private val navigationBackStack = BackStack.of(appStartDestinationId)
        lateinit var activity: Activity
        lateinit var fragmentManager: FragmentManager
        private var listener: OnNavigationItemChanged? = null
        private var navGraphProvider: NavGraphProvider? = null
    
        interface OnNavigationItemChanged {
            fun onItemChanged(itemId: Int)
        }
    
        interface NavGraphProvider {
            @NavigationRes
            fun getNavGraphId(itemId: Int): Int
        }
    
        init {
            var ctx = context
            while (ctx is ContextWrapper) {
                if (ctx is Activity) {
                    activity = ctx
                    fragmentManager = (activity as FragmentActivity).supportFragmentManager
                    break
                }
                ctx = ctx.baseContext
            }
        }
    
        fun setOnItemNavigationChanged(listener: (itemId: Int) -> Unit) {
            this.listener = object : OnNavigationItemChanged {
                override fun onItemChanged(itemId: Int) {
                    listener.invoke(itemId)
                }
            }
        }
    
        fun setNavGraphProvider(provider: NavGraphProvider) {
            navGraphProvider = provider
        }
    
        fun onNavigationItemReselected(item: MenuItem) {
            // If the user press a second time the navigation button, we pop the back stack to the root
            activity.findNavController(containerId).popBackStack(item.itemId, false)
        }
    
        fun onNavigationItemSelected(itemId: Int = navigationBackStack.last()): Boolean {
    
            // Replace fragment representing a navigation item
            val fragment = fragmentManager.findFragmentByTag(itemId.toString())
                    ?: NavHostFragment.create(navGraphProvider?.getNavGraphId(itemId)
                            ?: throw RuntimeException("You need to set up a NavGraphProvider with " +
                                    "BottomNavController#setNavGraphProvider")
                    )
            fragmentManager.beginTransaction()
                    .setCustomAnimations(
                            R.anim.nav_default_enter_anim,
                            R.anim.nav_default_exit_anim,
                            R.anim.nav_default_pop_enter_anim,
                            R.anim.nav_default_pop_exit_anim
                    )
                    .replace(containerId, fragment, itemId.toString())
                    .addToBackStack(null)
                    .commit()
    
            // Add to back stack
            navigationBackStack.moveLast(itemId)
    
            listener?.onItemChanged(itemId)
    
            return true
        }
    
        fun onBackPressed() {
            val childFragmentManager = fragmentManager.findFragmentById(containerId)!!
                    .childFragmentManager
            when {
                // We should always try to go back on the child fragment manager stack before going to
                // the navigation stack. It's important to use the child fragment manager instead of the
                // NavController because if the user change tabs super fast commit of the
                // supportFragmentManager may mess up with the NavController child fragment manager back
                // stack
                childFragmentManager.popBackStackImmediate() -> {
                }
                // Fragment back stack is empty so try to go back on the navigation stack
                navigationBackStack.size > 1 -> {
                    // Remove last item from back stack
                    navigationBackStack.removeLast()
    
                    // Update the container with new fragment
                    onNavigationItemSelected()
                }
                // If the stack has only one and it's not the navigation home we should
                // ensure that the application always leave from startDestination
                navigationBackStack.last() != appStartDestinationId -> {
                    navigationBackStack.removeLast()
                    navigationBackStack.add(0, appStartDestinationId)
                    onNavigationItemSelected()
                }
                // Navigation stack is empty, so finish the activity
                else -> activity.finish()
            }
        }
    
        private class BackStack : ArrayList<Int>() {
            companion object {
                fun of(vararg elements: Int): BackStack {
                    val b = BackStack()
                    b.addAll(elements.toTypedArray())
                    return b
                }
            }
    
            fun removeLast() = removeAt(size - 1)
            fun moveLast(item: Int) {
                remove(item)
                add(item)
            }
        }
    }
    
    // Convenience extension to set up the navigation
    fun BottomNavigationView.setUpNavigation(bottomNavController: BottomNavController, onReselect: ((menuItem: MenuItem) -> Unit)? = null) {
        setOnNavigationItemSelectedListener {
            bottomNavController.onNavigationItemSelected(it.itemId)
        }
        setOnNavigationItemReselectedListener {
            bottomNavController.onNavigationItemReselected(it)
            onReselect?.invoke(it)
        }
        bottomNavController.setOnItemNavigationChanged { itemId ->
            menu.findItem(itemId).isChecked = true
        }
    }
    

    Do your layout main.xml like this:

    <androidx.constraintlayout.widget.ConstraintLayout 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"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <FrameLayout
            android:id="@+id/container"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            app:layout_constraintBottom_toTopOf="@id/bottomNavigationView"
            app:layout_constraintTop_toTopOf="parent" />
    
        <com.google.android.material.bottomnavigation.BottomNavigationView
            android:id="@+id/bottomNavigationView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="0dp"
            android:layout_marginEnd="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:menu="@menu/navigation" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    

    Use on your activity like this:

    class MainActivity : AppCompatActivity(),
            BottomNavController.NavGraphProvider  {
    
        private val navController by lazy(LazyThreadSafetyMode.NONE) {
            Navigation.findNavController(this, R.id.container)
        }
    
        private val bottomNavController by lazy(LazyThreadSafetyMode.NONE) {
            BottomNavController(this, R.id.container, R.id.navigation_feed)
        }
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.main)
    
            bottomNavController.setNavGraphProvider(this)
            bottomNavigationView.setUpNavigation(bottomNavController)
            if (savedInstanceState == null) bottomNavController
                    .onNavigationItemSelected()
    
            // do your things...
        }
    
        override fun getNavGraphId(itemId: Int) = when (itemId) {
            R.id.navigation_feed -> R.navigation.navigation_feed
            R.id.navigation_explore -> R.navigation.navigation_explore
            R.id.navigation_profile -> R.navigation.navigation_profile
            else -> R.navigation.navigation_feed
        }
    
        override fun onSupportNavigateUp(): Boolean = navController
                .navigateUp()
    
        override fun onBackPressed() = bottomNavController.onBackPressed()
    }
    
    0 讨论(0)
  • 2020-12-12 11:27

    The key point to have a proper back stack that keeps state's is to have NavHostFragment's which has childFragmentManager and their own backstack. Extension file of Navigation component's advanced sample actually does this.

    /**
     * Manages the various graphs needed for a [BottomNavigationView].
     *
     * This sample is a workaround until the Navigation Component supports multiple back stacks.
     */
    fun BottomNavigationView.setupWithNavController(
        navGraphIds: List<Int>,
        fragmentManager: FragmentManager,
        containerId: Int,
        intent: Intent
    ): LiveData<NavController> {
    
        // Map of tags
        val graphIdToTagMap = SparseArray<String>()
        // Result. Mutable live data with the selected controlled
        val selectedNavController = MutableLiveData<NavController>()
    
        var firstFragmentGraphId = 0
    
        // First create a NavHostFragment for each NavGraph ID
        navGraphIds.forEachIndexed { index, navGraphId ->
            val fragmentTag = getFragmentTag(index)
    
            // Find or create the Navigation host fragment
            val navHostFragment = obtainNavHostFragment(
                fragmentManager,
                fragmentTag,
                navGraphId,
                containerId
            )
    
            // Obtain its id
            val graphId = navHostFragment.navController.graph.id
    
            if (index == 0) {
                firstFragmentGraphId = graphId
            }
    
            // Save to the map
            graphIdToTagMap[graphId] = fragmentTag
    
            // Attach or detach nav host fragment depending on whether it's the selected item.
            if (this.selectedItemId == graphId) {
                // Update livedata with the selected graph
                selectedNavController.value = navHostFragment.navController
                attachNavHostFragment(fragmentManager, navHostFragment, index == 0)
            } else {
                detachNavHostFragment(fragmentManager, navHostFragment)
            }
        }
    
        // Now connect selecting an item with swapping Fragments
        var selectedItemTag = graphIdToTagMap[this.selectedItemId]
        val firstFragmentTag = graphIdToTagMap[firstFragmentGraphId]
        var isOnFirstFragment = selectedItemTag == firstFragmentTag
    
        // When a navigation item is selected
        setOnNavigationItemSelectedListener { item ->
            // Don't do anything if the state is state has already been saved.
            if (fragmentManager.isStateSaved) {
                false
            } else {
                val newlySelectedItemTag = graphIdToTagMap[item.itemId]
                if (selectedItemTag != newlySelectedItemTag) {
                    // Pop everything above the first fragment (the "fixed start destination")
                    fragmentManager.popBackStack(
                        firstFragmentTag,
                        FragmentManager.POP_BACK_STACK_INCLUSIVE
                    )
                    val selectedFragment = fragmentManager.findFragmentByTag(newlySelectedItemTag)
                            as NavHostFragment
    
                    // Exclude the first fragment tag because it's always in the back stack.
                    if (firstFragmentTag != newlySelectedItemTag) {
                        // Commit a transaction that cleans the back stack and adds the first fragment
                        // to it, creating the fixed started destination.
                        fragmentManager.beginTransaction()
                            .attach(selectedFragment)
                            .setPrimaryNavigationFragment(selectedFragment)
                            .apply {
                                // Detach all other Fragments
                                graphIdToTagMap.forEach { _, fragmentTagIter ->
                                    if (fragmentTagIter != newlySelectedItemTag) {
                                        detach(fragmentManager.findFragmentByTag(firstFragmentTag)!!)
                                    }
                                }
                            }
                            .addToBackStack(firstFragmentTag)
                            .setCustomAnimations(
                                R.anim.nav_default_enter_anim,
                                R.anim.nav_default_exit_anim,
                                R.anim.nav_default_pop_enter_anim,
                                R.anim.nav_default_pop_exit_anim
                            )
                            .setReorderingAllowed(true)
                            .commit()
                    }
                    selectedItemTag = newlySelectedItemTag
                    isOnFirstFragment = selectedItemTag == firstFragmentTag
                    selectedNavController.value = selectedFragment.navController
                    true
                } else {
                    false
                }
            }
        }
    
        // Optional: on item reselected, pop back stack to the destination of the graph
        setupItemReselected(graphIdToTagMap, fragmentManager)
    
        // Handle deep link
        setupDeepLinks(navGraphIds, fragmentManager, containerId, intent)
    
        // Finally, ensure that we update our BottomNavigationView when the back stack changes
        fragmentManager.addOnBackStackChangedListener {
            if (!isOnFirstFragment && !fragmentManager.isOnBackStack(firstFragmentTag)) {
                this.selectedItemId = firstFragmentGraphId
            }
    
            // Reset the graph if the currentDestination is not valid (happens when the back
            // stack is popped after using the back button).
            selectedNavController.value?.let { controller ->
                if (controller.currentDestination == null) {
                    controller.navigate(controller.graph.id)
                }
            }
        }
        return selectedNavController
    }
    
    private fun BottomNavigationView.setupDeepLinks(
        navGraphIds: List<Int>,
        fragmentManager: FragmentManager,
        containerId: Int,
        intent: Intent
    ) {
        navGraphIds.forEachIndexed { index, navGraphId ->
            val fragmentTag = getFragmentTag(index)
    
            // Find or create the Navigation host fragment
            val navHostFragment = obtainNavHostFragment(
                fragmentManager,
                fragmentTag,
                navGraphId,
                containerId
            )
            // Handle Intent
            if (navHostFragment.navController.handleDeepLink(intent)
                && selectedItemId != navHostFragment.navController.graph.id
            ) {
                this.selectedItemId = navHostFragment.navController.graph.id
            }
        }
    }
    
    private fun BottomNavigationView.setupItemReselected(
        graphIdToTagMap: SparseArray<String>,
        fragmentManager: FragmentManager
    ) {
        setOnNavigationItemReselectedListener { item ->
            val newlySelectedItemTag = graphIdToTagMap[item.itemId]
            val selectedFragment = fragmentManager.findFragmentByTag(newlySelectedItemTag)
                    as NavHostFragment
            val navController = selectedFragment.navController
            // Pop the back stack to the start destination of the current navController graph
            navController.popBackStack(
                navController.graph.startDestination, false
            )
        }
    }
    
    private fun detachNavHostFragment(
        fragmentManager: FragmentManager,
        navHostFragment: NavHostFragment
    ) {
        fragmentManager.beginTransaction()
            .detach(navHostFragment)
            .commitNow()
    }
    
    private fun attachNavHostFragment(
        fragmentManager: FragmentManager,
        navHostFragment: NavHostFragment,
        isPrimaryNavFragment: Boolean
    ) {
        fragmentManager.beginTransaction()
            .attach(navHostFragment)
            .apply {
                if (isPrimaryNavFragment) {
                    setPrimaryNavigationFragment(navHostFragment)
                }
            }
            .commitNow()
    
    }
    
    private fun obtainNavHostFragment(
        fragmentManager: FragmentManager,
        fragmentTag: String,
        navGraphId: Int,
        containerId: Int
    ): NavHostFragment {
        // If the Nav Host fragment exists, return it
        val existingFragment = fragmentManager.findFragmentByTag(fragmentTag) as NavHostFragment?
        existingFragment?.let { return it }
    
        // Otherwise, create it and return it.
        val navHostFragment = NavHostFragment.create(navGraphId)
        fragmentManager.beginTransaction()
            .add(containerId, navHostFragment, fragmentTag)
            .commitNow()
        return navHostFragment
    }
    
    private fun FragmentManager.isOnBackStack(backStackName: String): Boolean {
        val backStackCount = backStackEntryCount
        for (index in 0 until backStackCount) {
            if (getBackStackEntryAt(index).name == backStackName) {
                return true
            }
        }
        return false
    }
    
    private fun getFragmentTag(index: Int) = "bottomNavigation#$index"
    

    Important part here is to obtain NavHostFragment if it does not exist in back stack with the function above and add it to back stack. commitNow is synchronous unlike commit

    private fun obtainNavHostFragment( fragmentManager: FragmentManager, fragmentTag: String, navGraphId: Int, containerId: Int ): NavHostFragment { // If the Nav Host fragment exists, return it val existingFragment = fragmentManager.findFragmentByTag(fragmentTag) as NavHostFragment? existingFragment?.let { return it }

    // Otherwise, create it and return it.
    val navHostFragment = NavHostFragment.create(navGraphId)
    fragmentManager.beginTransaction()
        .add(containerId, navHostFragment, fragmentTag)
        .commitNow()
    return navHostFragment
    

    }

    I built one using the NavigationExtension above which looks like this

    with nested navigation.

    Navigation graphs are similar, so only i add one

    nav_graph_home.xml

    <?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"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/nav_graph_home"
        app:startDestination="@id/homeFragment1">
    
    
        <fragment
            android:id="@+id/homeFragment1"
            android:name="com.smarttoolfactory.tutorial5_3navigationui_bottomnavigation_nestednavigation.blankfragment.HomeFragment1"
            android:label="HomeFragment1"
            tools:layout="@layout/fragment_home1">
            <action
                android:id="@+id/action_homeFragment1_to_homeFragment2"
                app:destination="@id/homeFragment2" />
        </fragment>
    
        <fragment
            android:id="@+id/homeFragment2"
            android:name="com.smarttoolfactory.tutorial5_3navigationui_bottomnavigation_nestednavigation.blankfragment.HomeFragment2"
            android:label="HomeFragment2"
            tools:layout="@layout/fragment_home2">
            <action
                android:id="@+id/action_homeFragment2_to_homeFragment3"
                app:destination="@id/homeFragment3" />
        </fragment>
    
        <fragment
            android:id="@+id/homeFragment3"
            android:name="com.smarttoolfactory.tutorial5_3navigationui_bottomnavigation_nestednavigation.blankfragment.HomeFragment3"
            android:label="HomeFragment3"
            tools:layout="@layout/fragment_home3" >
            <action
                android:id="@+id/action_homeFragment3_to_homeFragment1"
                app:destination="@id/homeFragment1"
                app:popUpTo="@id/homeFragment1"
                app:popUpToInclusive="true" />
        </fragment>
    
    </navigation>
    

    Menu for bottom navigation

    menu_bottom_nav.xml

    <?xml version="1.0" encoding="utf-8"?>
    <menu xmlns:android="http://schemas.android.com/apk/res/android">
    
        <item
                android:id="@+id/nav_graph_home"
                android:icon="@drawable/ic_baseline_home_24"
                android:title="Home"/>
    
        <item
                android:id="@+id/nav_graph_dashboard"
                android:icon="@drawable/ic_baseline_dashboard_24"
                android:title="Dashboard"/>
    
        <item
                android:id="@+id/nav_graph_notification"
                android:icon="@drawable/ic_baseline_notifications_24"
                android:title="Notification"/>
        
    </menu>
    

    Layout for MainActivity that contains FragmentContainerView and BottomNavigationView

    activiy_main.xml

    <?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"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <androidx.fragment.app.FragmentContainerView
            android:id="@+id/nav_host_container"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toTopOf="@+id/bottom_nav"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
        <com.google.android.material.bottomnavigation.BottomNavigationView
            android:id="@+id/bottom_nav"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"
            app:menu="@menu/menu_bottom_nav" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    

    MainActivity.kt

    class MainActivity : AppCompatActivity() {
    
        private var currentNavController: LiveData<NavController>? = null
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            supportFragmentManager.addOnBackStackChangedListener {
                val backStackEntryCount = supportFragmentManager.backStackEntryCount
                val fragments = supportFragmentManager.fragments
                val fragmentCount = fragments.size
    
    
                Toast.makeText(
                    this,
                    "MainActivity backStackEntryCount: $backStackEntryCount, fragmentCount: $fragmentCount, fragments: $fragments",
                    Toast.LENGTH_SHORT
                ).show()
            }
    
    
            if (savedInstanceState == null) {
                setupBottomNavigationBar()
            } // Else, need to wait for onRestoreInstanceState
        }
    
        override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
            super.onRestoreInstanceState(savedInstanceState)
            // Now that BottomNavigationBar has restored its instance state
            // and its selectedItemId, we can proceed with setting up the
            // BottomNavigationBar with Navigation
            setupBottomNavigationBar()
        }
    
        /**
         * Called on first creation and when restoring state.
         */
        private fun setupBottomNavigationBar() {
            val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottom_nav)
    
            val navGraphIds = listOf(
                R.navigation.nav_graph_home,
                R.navigation.nav_graph_dashboard,
                R.navigation.nav_graph_notification
            )
    
            // Setup the bottom navigation view with a list of navigation graphs
            val controller = bottomNavigationView.setupWithNavController(
                navGraphIds = navGraphIds,
                fragmentManager = supportFragmentManager,
                containerId = R.id.nav_host_container,
                intent = intent
            )
            // Whenever the selected controller changes, setup the action bar.
            controller.observe(this, Observer { navController ->
                setupActionBarWithNavController(navController)
            })
            currentNavController = controller
        }
    
        override fun onSupportNavigateUp(): Boolean {
            return currentNavController?.value?.navigateUp() ?: false
        }
    }
    

    Fragment layouts and classes are simple classes so i skipped them out.You can check out full sample i built, or Google's repository to examine extension for advanced navigation or or other samples.

    0 讨论(0)
提交回复
热议问题