在开发软件的时候,用到了bottomnavigation+fragment的框架,用过的人都知道,当点击下方的bottom的时候,会刷新当前fragment页面,且会重建当前fragment,如下所示
可以看见 当我点击bottom的icon的时候,当前fragment进行了重建,并且重新进行了网络请求。这在正常情况下是不合理的。
查看导航NavHostFragment可以看到,创建了一个FragmentNavigator
protected Navigator<? extends FragmentNavigator.Destination> createFragmentNavigator() {
return new FragmentNavigator(requireContext(), getChildFragmentManager(),
getContainerId());
}
在这个FragmentNavigator中的navigator方法中可以看到,当前框架是通过
ft.replace(mContainerId, frag);
replace方法来替换的。显然是这里的问题。
这种问题,谷歌当然替我们也想到了。在最新的2.4.0版本中得到了修改,支持了回退栈,不过2.4.0是alpha版本,并没有正式发布
但其实替换成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
android:id="@+id/nav_host_fragment_activity_main"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@id/nav_view"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/mobile_navigation" />
就是在这个fragment中构建了导航器navigator,我们可以自定义fragment继承NavHostFragment,然后在其中创建自定义的navigator,将核心的replace代码修改成show/hide方法
class FixNavHostFragment : NavHostFragment() {
/**
* @return 使用自己的FragmentNavigator
*/
override fun createFragmentNavigator(): Navigator<out FragmentNavigator.Destination?> {
return FixNavigator(requireContext(), childFragmentManager, containerId)
}
private val containerId: Int
get() {
val id = id
return if (id != 0 && id != View.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.show(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<Int>? = null
try {
val field = FragmentNavigator::class.java.getDeclaredField("mBackStack")
field.isAccessible = true
mBackStack = field[this] as ArrayDeque<Int>
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
val initialNavigation = mBackStack!!.isEmpty()
...省略代码
}
修改完成之后记得在xml中设置成自定义的fragment,且在activity中做如下修改
val nav: BottomNavigationView = binding.navView
val navController = findNavController(R.id.nav_host_fragment_activity_main)
nav.setOnItemSelectedListener {
navController.navigate(it.itemId)
true
}
// nav.setupWithNavController(navController)
完成,运行。