Navigation组件支持Fragment复用-2.4.0版本

参考

开发环境基于SDK 32,IDE是Android studio 2021.3.1

Navigation依赖的版本是2.4.0

implementation "androidx.navigation:navigation-fragment:2.4.0"
implementation "androidx.navigation:navigation-ui:2.4.0"

2.4.0版本跟2.3.x版本的不同之处在于需要重写的navigate方法不同,2.3.x的FragmentNavigator用的是java,2.4.0用的是kotlin。

1、创建一个FixFragmentNavigator继承FragmentNavigator,记得一定要加上注解@Navigator

@Navigator.Name("fixFragment")
class FixFragmentNavigator(
    private val mContext: Context,
    private val mFragmentManager: FragmentManager,
    private val mContainerId: Int
) : FragmentNavigator(
    mContext, mFragmentManager, mContainerId
)

2、重写navigate方法,把FragmentNavigator的代码copy过来

    override fun navigate(
        entries: List<NavBackStackEntry>,
        navOptions: NavOptions?,
        navigatorExtras: Navigator.Extras?
    ) {
        if (mFragmentManager.isStateSaved) {
            Log.i(
                TAG, "Ignoring navigate() call: FragmentManager has already saved its state"
            )
            return
        }
//        super.navigate(entries, navOptions, navigatorExtras)
        for (entry in entries) {
            navigate(entry, navOptions, navigatorExtras)
        }
    }

3、创建navigate方法,把FragmentNavigator的方法navigate(entry: NavBackStackEntry, navOptions: NavOptions?, navigatorExtras: Navigator.Extras?)也拷贝过来,可以看到逻辑跟2.3.x是一样的,不同的只是mBackStack变成了savedIds


    private fun navigate(
        entry: NavBackStackEntry,
        navOptions: NavOptions?,
        navigatorExtras: Navigator.Extras?
    ) {
        val backStack = state.backStack.value
        val initialNavigation = backStack.isEmpty()
        val restoreState = (
            navOptions != null && !initialNavigation &&
                navOptions.shouldRestoreState() &&
                savedIds.remove(entry.id)
            )
        if (restoreState) {
            // Restore back stack does all the work to restore the entry
            fragmentManager.restoreBackStack(entry.id)
            state.push(entry)
            return
        }
        val destination = entry.destination as Destination
        val args = entry.arguments
        var className = destination.className
        if (className[0] == '.') {
            className = context.packageName + className
        }
        val frag = fragmentManager.fragmentFactory.instantiate(context.classLoader, className)
        frag.arguments = args
        val ft = fragmentManager.beginTransaction()
        var enterAnim = navOptions?.enterAnim ?: -1
        var exitAnim = navOptions?.exitAnim ?: -1
        var popEnterAnim = navOptions?.popEnterAnim ?: -1
        var popExitAnim = navOptions?.popExitAnim ?: -1
        if (enterAnim != -1 || exitAnim != -1 || popEnterAnim != -1 || popExitAnim != -1) {
            enterAnim = if (enterAnim != -1) enterAnim else 0
            exitAnim = if (exitAnim != -1) exitAnim else 0
            popEnterAnim = if (popEnterAnim != -1) popEnterAnim else 0
            popExitAnim = if (popExitAnim != -1) popExitAnim else 0
            ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim)
        }
        ft.replace(containerId, frag)
        ft.setPrimaryNavigationFragment(frag)
        @IdRes val destId = destination.id
        // TODO Build first class singleTop behavior for fragments
        val isSingleTopReplacement = (
            navOptions != null && !initialNavigation &&
                navOptions.shouldLaunchSingleTop() &&
                backStack.last().destination.id == destId
            )
        val isAdded = when {
            initialNavigation -> {
                true
            }
            isSingleTopReplacement -> {
                // Single Top means we only want one instance on the back stack
                if (backStack.size > 1) {
                    // If the Fragment to be replaced is on the FragmentManager's
                    // back stack, a simple replace() isn't enough so we
                    // remove it from the back stack and put our replacement
                    // on the back stack in its place
                    fragmentManager.popBackStack(
                        entry.id,
                        FragmentManager.POP_BACK_STACK_INCLUSIVE
                    )
                    ft.addToBackStack(entry.id)
                }
                false
            }
            else -> {
                ft.addToBackStack(entry.id)
                true
            }
        }
        if (navigatorExtras is Extras) {
            for ((key, value) in navigatorExtras.sharedElements) {
                ft.addSharedElement(key, value)
            }
        }
        ft.setReorderingAllowed(true)
        ft.commit()
        // The commit succeeded, update our view of the world
        if (isAdded) {
            state.push(entry)
        }
    }

4、反射获取savedIds,把replace改成show和hide。特别要注意的是NavHostFragment一定不要hide


    private fun navigate(
        entry: NavBackStackEntry,
        navOptions: NavOptions?,
        navigatorExtras: Navigator.Extras?
    ) {
        val savedIds = try {
            val targetClass: Class<*> = this.javaClass.superclass
            val obj = targetClass.cast(this) as FragmentNavigator
            val field = targetClass.getDeclaredField("savedIds")
            //修改访问限制
            field.isAccessible = true

            field[obj] as MutableSet<String>
        } catch (e: Exception) {
            e.printStackTrace()
            mutableSetOf()
        }

        //.........省略

//        val frag = mFragmentManager.fragmentFactory.instantiate(context.classLoader, className)

        //把类名作为tag,寻找已存在的Fragment
        //(如果想只针对个别fragment进行保活复用,可以在tag上做些标记比如加个前缀)
        var frag = mFragmentManager.findFragmentByTag(className)
        if (null == frag) {
            //不存在,则创建
            frag = mFragmentManager.fragmentFactory.instantiate(mContext.classLoader, className)
        }
       
        //.........省略

//        ft.replace(containerId, frag)

        //replace换成show和hide
        val fragments = mFragmentManager.fragments
        for (fragment in fragments) {
            //NavHostFragment不能hide
            if(fragment.isAdded && fragment.isVisible && fragment !is NavHostFragment) {
                ft.hide(fragment)
            }
        }
        if (!frag.isAdded) {
            ft.add(mContainerId, frag, className)
        }
        ft.show(frag)

        //.........省略
    }

然后其他的就跟Android-Jetpack笔记-Navigation之Fragment支持复用这篇文章的一样就可以了。

最后还是要感谢大佬们的分享的知识

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值