jasurbekdev

NavigationExtensions

Oct 23rd, 2020
293
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Kotlin 10.01 KB | None | 0 0
  1. /*
  2.  * Copyright 2019, The Android Open Source Project
  3.  *
  4.  * Licensed under the Apache License, Version 2.0 (the "License");
  5.  * you may not use this file except in compliance with the License.
  6.  * You may obtain a copy of the License at
  7.  *
  8.  *      http://www.apache.org/licenses/LICENSE-2.0
  9.  *
  10.  * Unless required by applicable law or agreed to in writing, software
  11.  * distributed under the License is distributed on an "AS IS" BASIS,
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13.  * See the License for the specific language governing permissions and
  14.  * limitations under the License.
  15.  */
  16.  
  17. package com.example.android.navigationadvancedsample
  18.  
  19. import android.content.Intent
  20. import android.util.SparseArray
  21. import android.widget.TextView
  22. import androidx.core.util.forEach
  23. import androidx.core.util.set
  24. import androidx.fragment.app.FragmentManager
  25. import androidx.lifecycle.LiveData
  26. import androidx.lifecycle.MutableLiveData
  27. import androidx.navigation.NavController
  28. import androidx.navigation.fragment.NavHostFragment
  29. import com.google.android.material.bottomnavigation.BottomNavigationView
  30.  
  31. /**
  32.  * Manages the various graphs needed for a [BottomNavigationView].
  33.  *
  34.  * This sample is a workaround until the Navigation Component supports multiple back stacks.
  35.  */
  36. fun BottomNavigationView.setupWithNavController(
  37.     navGraphIds: List<Int>,
  38.     fragmentManager: FragmentManager,
  39.     containerId: Int,
  40.     intent: Intent,
  41.     textView: TextView
  42. ): LiveData<NavController> {
  43.  
  44.     // Map of tags
  45.     val graphIdToTagMap = SparseArray<String>()
  46.     // Result. Mutable live data with the selected controlled
  47.     val selectedNavController = MutableLiveData<NavController>()
  48.  
  49.     var firstFragmentGraphId = 0
  50.  
  51.     // First create a NavHostFragment for each NavGraph ID
  52.     navGraphIds.forEachIndexed { index, navGraphId ->
  53.         val fragmentTag = getFragmentTag(index)
  54.  
  55.         // Find or create the Navigation host fragment
  56.         val navHostFragment = obtainNavHostFragment(
  57.             fragmentManager,
  58.             fragmentTag,
  59.             navGraphId,
  60.             containerId
  61.         )
  62.  
  63.         // Obtain its id
  64.         val graphId = navHostFragment.navController.graph.id
  65.  
  66.         if (index == 0) {
  67.             firstFragmentGraphId = graphId
  68.         }
  69.  
  70.         // Save to the map
  71.         graphIdToTagMap[graphId] = fragmentTag
  72.  
  73.         // Attach or detach nav host fragment depending on whether it's the selected item.
  74.         if (this.selectedItemId == graphId) {
  75.             // Update livedata with the selected graph
  76.             selectedNavController.value = navHostFragment.navController
  77.             attachNavHostFragment(fragmentManager, navHostFragment, index == 0)
  78.         } else {
  79.             detachNavHostFragment(fragmentManager, navHostFragment)
  80.         }
  81.     }
  82.  
  83.     // Now connect selecting an item with swapping Fragments
  84.     var selectedItemTag = graphIdToTagMap[this.selectedItemId]
  85.     val firstFragmentTag = graphIdToTagMap[firstFragmentGraphId]
  86.     var isOnFirstFragment = selectedItemTag == firstFragmentTag
  87.  
  88.     // When a navigation item is selected
  89.     setOnNavigationItemSelectedListener { item ->
  90.         // Don't do anything if the state is state has already been saved.
  91.         if (fragmentManager.isStateSaved) {
  92.             for (entry in 0 until fragmentManager.backStackEntryCount) {
  93.                 textView.text = "${textView.text}\n${fragmentManager.getBackStackEntryAt(entry).name}"
  94.             }
  95.             textView.append("\n")
  96.             false
  97.         } else {
  98.             val newlySelectedItemTag = graphIdToTagMap[item.itemId]
  99.             if (selectedItemTag != newlySelectedItemTag) {
  100.                 // Pop everything above the first fragment (the "fixed start destination")
  101.                 fragmentManager.popBackStack(firstFragmentTag, FragmentManager.POP_BACK_STACK_INCLUSIVE)
  102.                 val selectedFragment = fragmentManager.findFragmentByTag(newlySelectedItemTag)
  103.                     as NavHostFragment
  104.  
  105.                 // Exclude the first fragment tag because it's always in the back stack.
  106.                 if (firstFragmentTag != newlySelectedItemTag) {
  107.                     // Commit a transaction that cleans the back stack and adds the first fragment
  108.                     // to it, creating the fixed started destination.
  109.                     fragmentManager.beginTransaction()
  110.                         .setCustomAnimations(
  111.                             R.anim.nav_default_enter_anim,
  112.                             R.anim.nav_default_exit_anim,
  113.                             R.anim.nav_default_pop_enter_anim,
  114.                             R.anim.nav_default_pop_exit_anim)
  115.                         .attach(selectedFragment)
  116.                         .setPrimaryNavigationFragment(selectedFragment)
  117.                         .apply {
  118.                             // Detach all other Fragments
  119.                             graphIdToTagMap.forEach { _, fragmentTagIter ->
  120.                                 if (fragmentTagIter != newlySelectedItemTag) {
  121.                                     detach(fragmentManager.findFragmentByTag(firstFragmentTag)!!)
  122.                                 }
  123.                             }
  124.                         }
  125.                         .addToBackStack(firstFragmentTag)
  126.                         .setReorderingAllowed(true)
  127.                         .commit()
  128.                 }
  129.                 selectedItemTag = newlySelectedItemTag
  130.                 isOnFirstFragment = selectedItemTag == firstFragmentTag
  131.                 selectedNavController.value = selectedFragment.navController
  132.                 for (entry in 0 until fragmentManager.backStackEntryCount) {
  133.                     textView.text = "${textView.text}\n${fragmentManager.getBackStackEntryAt(entry).name}"
  134.                 }
  135.                 textView.append("\n")
  136.                 true
  137.             } else {
  138.                 for (entry in 0 until fragmentManager.backStackEntryCount) {
  139.                     textView.text = "${textView.text}\n${fragmentManager.getBackStackEntryAt(entry).name}"
  140.                 }
  141.                 textView.append("\n")
  142.                 false
  143.             }
  144.         }
  145.     }
  146.  
  147.     // Optional: on item reselected, pop back stack to the destination of the graph
  148.     setupItemReselected(graphIdToTagMap, fragmentManager)
  149.  
  150.     // Handle deep link
  151.     setupDeepLinks(navGraphIds, fragmentManager, containerId, intent)
  152.  
  153.     // Finally, ensure that we update our BottomNavigationView when the back stack changes
  154.     fragmentManager.addOnBackStackChangedListener {
  155.         if (!isOnFirstFragment && !fragmentManager.isOnBackStack(firstFragmentTag)) {
  156.             this.selectedItemId = firstFragmentGraphId
  157.         }
  158.  
  159.         // Reset the graph if the currentDestination is not valid (happens when the back
  160.         // stack is popped after using the back button).
  161.         selectedNavController.value?.let { controller ->
  162.             if (controller.currentDestination == null) {
  163.                 controller.navigate(controller.graph.id)
  164.             }
  165.         }
  166.     }
  167.     return selectedNavController
  168. }
  169.  
  170. private fun BottomNavigationView.setupDeepLinks(
  171.     navGraphIds: List<Int>,
  172.     fragmentManager: FragmentManager,
  173.     containerId: Int,
  174.     intent: Intent
  175. ) {
  176.     navGraphIds.forEachIndexed { index, navGraphId ->
  177.         val fragmentTag = getFragmentTag(index)
  178.  
  179.         // Find or create the Navigation host fragment
  180.         val navHostFragment = obtainNavHostFragment(
  181.             fragmentManager,
  182.             fragmentTag,
  183.             navGraphId,
  184.             containerId
  185.         )
  186.         // Handle Intent
  187.         if (navHostFragment.navController.handleDeepLink(intent)
  188.                 && selectedItemId != navHostFragment.navController.graph.id) {
  189.             this.selectedItemId = navHostFragment.navController.graph.id
  190.         }
  191.     }
  192. }
  193.  
  194. private fun BottomNavigationView.setupItemReselected(
  195.     graphIdToTagMap: SparseArray<String>,
  196.     fragmentManager: FragmentManager
  197. ) {
  198.     setOnNavigationItemReselectedListener { item ->
  199.         val newlySelectedItemTag = graphIdToTagMap[item.itemId]
  200.         val selectedFragment = fragmentManager.findFragmentByTag(newlySelectedItemTag)
  201.             as NavHostFragment
  202.         val navController = selectedFragment.navController
  203.         // Pop the back stack to the start destination of the current navController graph
  204.         navController.popBackStack(
  205.             navController.graph.startDestination, false
  206.         )
  207.     }
  208. }
  209.  
  210. private fun detachNavHostFragment(
  211.     fragmentManager: FragmentManager,
  212.     navHostFragment: NavHostFragment
  213. ) {
  214.     fragmentManager.beginTransaction()
  215.         .detach(navHostFragment)
  216.         .commitNow()
  217. }
  218.  
  219. private fun attachNavHostFragment(
  220.     fragmentManager: FragmentManager,
  221.     navHostFragment: NavHostFragment,
  222.     isPrimaryNavFragment: Boolean
  223. ) {
  224.     fragmentManager.beginTransaction()
  225. //        .attach(navHostFragment)
  226.         .apply {
  227.             if (isPrimaryNavFragment) {
  228.                 setPrimaryNavigationFragment(navHostFragment)
  229.             }
  230.         }
  231.         .commitNow()
  232.  
  233. }
  234.  
  235. private fun obtainNavHostFragment(
  236.     fragmentManager: FragmentManager,
  237.     fragmentTag: String,
  238.     navGraphId: Int,
  239.     containerId: Int
  240. ): NavHostFragment {
  241.     // If the Nav Host fragment exists, return it
  242.     val existingFragment = fragmentManager.findFragmentByTag(fragmentTag) as NavHostFragment?
  243.     existingFragment?.let { return it }
  244.  
  245.     // Otherwise, create it and return it.
  246.     val navHostFragment = NavHostFragment.create(navGraphId)
  247.     fragmentManager.beginTransaction()
  248.         .add(containerId, navHostFragment, fragmentTag)
  249.         .commitNow()
  250.     return navHostFragment
  251. }
  252.  
  253. private fun FragmentManager.isOnBackStack(backStackName: String): Boolean {
  254.     val backStackCount = backStackEntryCount
  255.     for (index in 0 until backStackCount) {
  256.         if (getBackStackEntryAt(index).name == backStackName) {
  257.             return true
  258.         }
  259.     }
  260.     return false
  261. }
  262.  
  263. private fun getFragmentTag(index: Int) = "bottomNavigation#$index"
  264.  
Add Comment
Please, Sign In to add comment