android 解决2.3.5bottomnavigation的fragment重建的问题

家电维修 2023-07-16 19:17www.caominkang.com家电维修技术

在开发软件的时候,用到了bottomnavigation+fragment的框架,用过的人都知道,当点击下方的bottom的时候,会刷新当前fragment页面,且会重建当前fragment,如下所示

可以看见 当我点击bottom的icon的时候,当前fragment进行了重建,并且重新进行了网络请求。这在正常情况下是不合理的。

查看导航NavHostFragment可以看到,创建了一个FragmentNavigator

 protected Navigator 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)

完成,运行。

Copyright © 2016-2025 www.jianfeikang.com 建飞家电维修 版权所有 Power by