前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Navigation——Fragment创建新的实例问题详解

Navigation——Fragment创建新的实例问题详解

作者头像
Demo_Yang
发布2019-05-15 15:10:09
2.2K0
发布2019-05-15 15:10:09
举报
文章被收录于专栏:yang0rangeyang0range

背景

上一篇文章Navigation——Fragment创建新的实例问题,我们简述了我们在使用Navigation遇到的Fragment创建新的实例的问题。接下来,我们在这篇文章就来解决一下我们遇到的这个问题

源码追踪

打开 MainActivity 的布局文件,我们可以看到在布局文件当中, Frangmet 这里,有一个来自于 androidx的NavHostFragment。

代码语言:javascript
复制
   <fragment
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/my_nav_host_fragment"
            android:name="androidx.navigation.fragment.NavHostFragment"
            app:navGraph="@navigation/nav_graph"
            app:defaultNavHost="true"
    />

进入 NavHostFragment 的源码,我们一探究竟。 在 NavHostFragment 源码的 onCreate 方法当中,我们找到了答案。 完整的 onCreat 方法

代码语言:javascript
复制
  @CallSuper
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        final Context context = requireContext();

        mNavController = new NavController(context);
        mNavController.getNavigatorProvider().addNavigator(createFragmentNavigator());

        Bundle navState = null;
        if (savedInstanceState != null) {
            navState = savedInstanceState.getBundle(KEY_NAV_CONTROLLER_STATE);
            if (savedInstanceState.getBoolean(KEY_DEFAULT_NAV_HOST, false)) {
                mDefaultNavHost = true;
                requireFragmentManager().beginTransaction()
                        .setPrimaryNavigationFragment(this)
                        .commit();
            }
        }

        if (navState != null) {
            // Navigation controller state overrides arguments
            mNavController.restoreState(navState);
        }
        if (mGraphId != 0) {
            // Set from onInflate()
            mNavController.setGraph(mGraphId);
        } else {
            // See if it was set by NavHostFragment.create()
            final Bundle args = getArguments();
            final int graphId = args != null ? args.getInt(KEY_GRAPH_ID) : 0;
            final Bundle startDestinationArgs = args != null
                    ? args.getBundle(KEY_START_DESTINATION_ARGS)
                    : null;
            if (graphId != 0) {
                mNavController.setGraph(graphId, startDestinationArgs);
            }
        }
    }

在这其中,有一行

代码语言:javascript
复制
 mNavController = new NavController(context);
        mNavController.getNavigatorProvider().addNavigator(createFragmentNavigator());

也就是说,只要添加一个 Fragment 就会在 NavController 当中 Add 一个 FragmentNavigator ,而在 createFragmentNavigator 方法当中,Navigator 方法里对 Fragment 进行了处理

代码语言:javascript
复制
    @NonNull
    protected Navigator<? extends FragmentNavigator.Destination> createFragmentNavigator() {
        return new FragmentNavigator(requireContext(), getChildFragmentManager(), getId());
    }

解决问题

既然,我们都已经找到导致没次都创建新的实例的根结所在,那么我们现在来解决一下问题。1那么我们只需要重新写一个NavHostFragment的createFragmentNavigator的方法,来满足我们的要求。

代码语言:javascript
复制
/**
 * 复用的NavHostFragment (默认不是复用 引起一个问题就是 不保存fragment状态)
 */
class TabNavHostFragment : NavHostFragment() {
    override fun createFragmentNavigator(): Navigator<out FragmentNavigator.Destination> {
        return MyNavigator(requireContext(), childFragmentManager, id)
    }

    //参考相关链接
    // https://stackoverflow.com/questions/50485988/is-there-a-way-to-keep-fragment-alive-when-using-bottomnavigationview-with-new-n/51684125
    @Navigator.Name("tab_fragment")  // 这个名称在 navigation.xml 当中使用。
    open class MyNavigator(var mContext: Context, var mFragmentManager: FragmentManager, var mContainerId: Int) :
        FragmentNavigator(mContext, mFragmentManager, mContainerId) {
        override fun navigate(
            destination: Destination,
            args: Bundle?,
            navOptions: NavOptions?,
            navigatorExtras: Navigator.Extras?
        ): NavDestination? {
            try {
                //反射获取mBackStack mIsPendingBackStackOperation
                val mBackStackField = FragmentNavigator::class.java.getDeclaredField("mBackStack")
                mBackStackField.isAccessible = true
                var mBackStack: ArrayDeque<Int> = mBackStackField.get(this) as ArrayDeque<Int>

                val mIsPendingBackStackOperationField =
                    FragmentNavigator::class.java.getDeclaredField("mIsPendingBackStackOperation")
                mIsPendingBackStackOperationField.isAccessible = true
                var mIsPendingBackStackOperation: Boolean = mIsPendingBackStackOperationField.get(this) as Boolean

                if (mFragmentManager.isStateSaved) {
                    //Log.i("TAG", "Ignoring navigate() call: FragmentManager has already" + " saved its state")
                    return null
                }
                var className = destination.className
                if (className[0] == '.') {
                    className = mContext.packageName + className
                }

                val ft = mFragmentManager.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)
                }

                val tag = destination.id.toString()
                //ft.replace(mContainerId, frag)

                val currentFragment = mFragmentManager.primaryNavigationFragment
                if (currentFragment != null) {
                    ft.hide(currentFragment)
                }

                var frag = mFragmentManager.findFragmentByTag(tag)
                if (frag == null) {
                    frag = instantiateFragment(
                        mContext, mFragmentManager,
                        className, args
                    )
                    frag.arguments = args
                    ft.add(mContainerId, frag, tag)
                } else {
                    ft.show(frag)
                }

                ft.setPrimaryNavigationFragment(frag)

                @IdRes val destId = destination.id
                val initialNavigation = mBackStack.isEmpty()
                // TODO Build first class singleTop behavior for fragments
                val isSingleTopReplacement = (navOptions != null && !initialNavigation
                        && navOptions.shouldLaunchSingleTop()
                        && mBackStack.peekLast().toInt() == destId)

                val isAdded: Boolean
                if (initialNavigation) {
                    isAdded = true
                } else if (isSingleTopReplacement) {
                    // Single Top means we only want one instance on the back stack
                    if (mBackStack.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
                        mFragmentManager.popBackStack(
                            generateMyBackStackName(mBackStack.size, mBackStack.peekLast()),
                            FragmentManager.POP_BACK_STACK_INCLUSIVE
                        )
                        ft.addToBackStack(generateMyBackStackName(mBackStack.size, destId))
                        mIsPendingBackStackOperation = true
                        mIsPendingBackStackOperationField.set(this, true)
                    }
                    isAdded = false
                } else {
                    ft.addToBackStack(generateMyBackStackName(mBackStack.size + 1, destId))
                    mIsPendingBackStackOperation = true
                    mIsPendingBackStackOperationField.set(this, true)
                    isAdded = true
                }
                if (navigatorExtras is Extras) {
                    val extras = navigatorExtras as Extras?
                    for ((key, value) in extras!!.sharedElements) {
                        ft.addSharedElement(key, value)
                    }
                }
                ft.setReorderingAllowed(true)
                ft.commit()
                // The commit succeeded, update our view of the world
                return if (isAdded) {
                    mBackStack.add(destId)
                    destination
                } else {
                    null
                }
            } catch (e: Throwable) {
                return super.navigate(destination, args, navOptions, navigatorExtras)
            }
        }

        private fun generateMyBackStackName(backStackIndex: Int, destId: Int): String {
            return "$backStackIndex-$destId"
        }
    }
}

然后,在我们的代码当中,引入我们自定修改之后的这个 TabNavHostFragment 在 MianActivity 的布局文件当中修改为

代码语言:javascript
复制
<fragment
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/my_nav_host_fragment"
            android:name="com.demo.navigationcomponent.TabNavHostFragment"
            app:navGraph="@navigation/nav_graph"
            app:defaultNavHost="true"
    />

然后,在 nav_graph.xml文件当中,修改为:

代码语言:javascript
复制
    <tab_fragment android:id="@+id/oneFragment" android:name="com.demo.navigationcomponent.OneFragment"
              android:label="fragment_one" tools:layout="@layout/fragment_one">
        <action android:id="@+id/action_oneFragment_to_twoFragment"
                app:destination="@id/twoFragment"
                app:popUpTo="@id/oneFragment"
                app:popUpToInclusive="true"/>
    </tab_fragment>

至此,我们大功告成了,当我们在添加新的 Fragment 的时候,当已经创建过 Fragment 的实例的时候,就不会创建新的实例了。

最后

通过以上的方法,可以实现我们想要的效果,但是我认为这只是一个临时的解决方案,修改源码这种方式,并不是一个特别好的解决方案。如果有其他更好的方法,方案,欢迎给我公共号「朝阳杨大爷」给我留言,讨论。

GitHub 地址

代码,我已经放到了 GitHub 上了欢迎下载 Star https://github.com/yang0range/NavigationComponent/tree/Branch_One

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019.05.08 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 源码追踪
  • 解决问题
  • 最后
  • GitHub 地址
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档