android 解决2.3.5bottomnavigation的fragment重建的问题
在开发软件的时候,用到了bottomnavigation+fragment的框架,用过的人都知道,当点击下方的bottom的时候,会刷新当前fragment页面,且会重建当前fragment,如下所示
可以看见 当我点击bottom的icon的时候,当前fragment进行了重建,并且重新进行了网络请求。这在正常情况下是不合理的。
查看导航NavHostFragment可以看到,创建了一个FragmentNavigator
protected Navigator extends FragmentNavigator.Destination> createFragmentNavigator() { return ne FragmentNavigator(requireContext(), getChildFragmentManager(), getContainerId()); }
在这个FragmentNavigator中的navigator方法中可以看到,当前框架是通过
ft.replace(mContainerId, frag);
replace方法来替换的。显然是这里的问题。
这种问题,谷歌替我们也想到了。在最新的2.4.0版本中得到了修改,支持了回退栈,不过2.4.0是alpha版本,并没有正式发布
Android Developers Navigation
但其实替换成2.4.0好像也没有完全符合我的需求。其实际效果如下
1.当我点击当前页面的时候,fragment没有进行重建
2.当我点击其他页面然后切换回来的时候,fragment进行了重建,并且重新执行网络请求
3.当此页面存在下啦加载等功能的时候,如不做特殊处理,来回切换页面,会使数据混乱
查看源码可以看到
增加了一个集合用于存放fragment实例,切换的时候保存了当前fragment的一些状态,在重建的时候恢复,实际上仍然使用replace方法切换fragment
ft.replace(containerId, frag)
如此功能,并不能简单方便的实现我们正常使用的切换功能。且2.4.0仍然alpha版本,所以在2.3.5中进行部分修改
在开发中使用navgation,需要在主activity中的fragment标签中添加 NavHostFragment
就是在这个fragment中构建了导航器navigator,我们可以自定义fragment继承NavHostFragment,然后在其中创建自定义的navigator,将核心的replace代码修改成sho/hide方法
class FixNavHostFragment : NavHostFragment() { override fun createFragmentNavigator(): Navigator{ return FixNavigator(requireContext(), childFragmentManager, containerId) } private val containerId: Int get() { val id = id return if (id != 0 && id != Vie.NO_ID) { id } else R.id.nav_host_fragment_container } }
这个代码很简单看function名字就可以知道其作用,用来create一个navigator,创建一个自定义navigator继承于FragmentNavigator
@Navigator.Name("fragment") class FixNavigator( private val mContext: Context, private val mFragmentManager: FragmentManager, private val mContainerId: Int ) : FragmentNavigator(mContext, mFragmentManager, mContainerId) { }
不要忘记添加注解Navigator.Name 用来标记这个navigation导航器
主要重写其中的navigator方法即可
其中大多数代码还是CV过来就可以了,主要修改replace方法,如下
@Navigator.Name("fragment") class FixNavigator( private val mContext: Context, private val mFragmentManager: FragmentManager, private val mContainerId: Int ) : FragmentNavigator(mContext, mFragmentManager, mContainerId) { ...省略代码 //获取当前需要显示的fragment val fragment = mFragmentManager.primaryNavigationFragment if (fragment != null) { ft.setMaxLifecycle(fragment, Lifecycle.State.STARTED) ft.hide(fragment) } var frag: Fragment? = null //获取id val tag = destination.id.toString() frag = mFragmentManager.findFragmentByTag(tag) if (frag != null) { ft.setMaxLifecycle(frag, Lifecycle.State.RESUMED) ft.sho(frag) } else { frag = instantiateFragment(mContext, mFragmentManager, className, args) frag.arguments = args ft.add(mContainerId, frag, tag) } //ft.replace(mContainerId, frag); ft.setPrimaryNavigationFragment(frag) @IdRes val destId = destination.id //父类中的mbackStack为private的,需要通过反射获取 var mBackStack: ArrayDeque? = null try { val field = FragmentNavigator::class.java.getDeclaredField("mBackStack") field.isAessible = true mBackStack = field[this] as ArrayDeque } catch (e: NoSuchFieldException) { e.printStackTrace() } catch (e: IllegalAessException) { e.printStackTrace() } val initialNavigation = mBackStack!!.isEmpty() ...省略代码 }
修改完成之后记得在xml中设置成自定义的fragment,且在activity中做如下修改
val nav: BottomNavigationVie = binding.navVie val navController = findNavController(R.id.nav_host_fragment_activity_main) nav.setOnItemSelectedListener { navController.navigate(it.itemId) true } // nav.setupWithNavController(navController)
完成,运行。