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

在开发软件的时候,用到了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版本,并没有正式发布

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

完成,运行。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值