栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

背景

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

源码追踪

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

   

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

  @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);
     }
 }
    }

在这其中,有一行

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

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

    @NonNull
    protected Navigator createFragmentNavigator() {
 return new FragmentNavigator(requireContext(), getChildFragmentManager(), getId());
    }

解决问题

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

class TabNavHostFragment : NavHostFragment() {
    override fun createFragmentNavigator(): Navigator {
 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 = mBackStackField.get(this) as ArrayDeque

  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 的布局文件当中修改为



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

    
 
    

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

最后

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

GitHub 地址

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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/239279.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号