【问题标题】:Android navigation component: how save fragment stateAndroid导航组件:如何保存片段状态
【发布时间】:2019-10-05 08:09:02
【问题描述】:

我使用bottomNavigationView 和导航组件。请告诉我如何在切换到另一个选项卡并返回旧选项卡后不破坏片段?例如,我有三个选项卡 - A、B、C。我的开始选项卡是 A。导航到 B 后,然后返回 A。当我返回选项卡 A 时,我不希望重新创建它。怎么做?谢谢

【问题讨论】:

    标签: android navigation android-architecture-navigation


    【解决方案1】:

    根据open issue,Navigation 不直接支持多个回栈 - 即,当您从 A 或 C 返回 B 时保存堆栈 B 的状态,因为 Fragments 不支持多个回栈。

    根据this comment:

    NavigationAdvancedSample 现已在https://github.com/googlesamples/android-architecture-components/tree/master/NavigationAdvancedSample 提供

    此示例使用多个 NavHostFragment,每个底部导航选项卡一个,以解决 Fragment API 在支持多个返回堆栈方面的当前限制。

    我们将继续使用 Fragment API 以支持多个返回堆栈,并在创建后将 Navigation API 插入其中,这将不再需要像 NavigationExtensions.kt 文件这样的东西。我们将继续使用此问题来跟踪这项工作。

    因此,您现在可以在您的应用中使用 NavigationAdvancedSample 方法并为问题加注星标,以便在解决基本问题并将直接支持添加到 Navigation 时获得更新。

    【讨论】:

    • 所以像在内存中保留 3 个屏幕这样的简单功能需要一个新的“高级”样本?你一定是在开玩笑
    • @Radu - 它不仅仅是三个片段,它是与每个选项卡相关联的整个后栈(以及 那些 片段中的每个片段的状态)。 FragmentManager 仅将事物的状态直接存储在后台堆栈中(即,您可以点击系统后退按钮以返回到它们),底部导航不是这种情况,您希望用户能够在选项卡之间交换不会丢失状态。
    • @AminKeshavarzian - 您也可以使用 <include> tag 引用单独的图表,而不是复制/粘贴。
    • 我工作的一个解决方案是在需要的每个片段中实现 ViewModel + LiveData
    • 我几乎放弃了导航架构组件。 Google 还应该提到他们的新 API 的局限性,这将使开发人员的生活更容易决定哪个 API 更适合长期使用。
    【解决方案2】:

    如果您可以处理销毁片段,但又想保存 ViewModel,则可以将其范围限定在 Navigation Graph 中:

    private val viewModel: FavouritesViewModel by 
        navGraphViewModels(R.id.mobile_navigation) {
            viewModelFactory
        }
    

    阅读更多here

    编辑

    正如@SpiralDev 所说,使用 Hilt 可以稍微简化一下:

    private val viewModel: MainViewModel by 
        navGraphViewModels(R.id.mobile_navigation) {
             defaultViewModelProviderFactory     
        }
    

    【讨论】:

    • 对于 DaggerHilt:private val viewModel: MainViewModel by navGraphViewModels(R.id.my_nav) { defaultViewModelProviderFactory }
    【解决方案3】:

    只使用2.4.0-alpha01或以上版本的导航组件

    【讨论】:

    • 工作正常,但它会清除所有变量。
    • 是的,你需要像往常一样在viewmodel上处理它,如果你使用scrollview不要忘记添加id。
    • 是的,保存 UI 状态效果很好。
    【解决方案4】:

    更新: 使用片段导航组件的最新版本,自行处理片段状态。见this sample

    旧:

    class BaseViewModel : ViewModel() {
    
        val bundleFromFragment = MutableLiveData<Bundle>()
    }
    
    
    class HomeViewModel : BaseViewModel () {
    
       ... HomeViewModel logic
    }
    

    在主页片段内(底部导航标签)

    private var viewModel: HomeViewModel by viewModels()
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
    
        viewModel.bundleFromFragment.observe(viewLifecycleOwner, Observer {
          
            val message = it.getString("ARGUMENT_MESSAGE", "")
           binding.edtName.text = message
        })
    }
    
    override fun onDestroyView() {
        super.onDestroyView()
        viewModel.bundleFromFragment.value = bundleOf(
            "ARGUMENT_MESSAGE" to binding.edtName.text.toString(),
            "SCROLL_POSITION" to binding.scrollable.scrollY
        )
    }
    

    您可以对底部导航中的所有片段执行此模式

    【讨论】:

      【解决方案5】:

      2021 年更新 使用 2.4.0-alpha05 或更高版本。 请勿使用此answer 或其他等。

      【讨论】:

        【解决方案6】:

        这可以使用片段显示/隐藏逻辑来实现。

        private val bottomFragmentMap = hashMapOf<Int, Fragment>()
        bottomFragmentMap[0] = FragmentA.newInstance()
        bottomFragmentMap[1] = FragmentB.newInstance()
        bottomFragmentMap[2] = FragmentC.newInstance()
        bottomFragmentMap[3] = FragmentD.newInstance()
        
        
        private fun loadFragment(fragmentIndex: Int) {
            val fragmentTransaction = childFragmentManager.beginTransaction()
        
            val bottomFragment = bottomFragmentMap[fragmentIndex]!!
        
            // first time case. Add to container
            if (!bottomFragment.isAdded) {
                fragmentTransaction.add(R.id.container, bottomFragment)
            }
        
            // hide remaining fragments
            for ((key, value) in bottomFragmentMap) {
                if (key == fragmentIndex) {
                    fragmentTransaction.show(value)
                } else if (value.isVisible) {
                    fragmentTransaction.hide(value)
                }
            }
            fragmentTransaction.commit()
        }
        

        【讨论】:

          【解决方案7】:

          在活动上声明片段并在 onCreate 方法上创建片段实例,然后在 updateFragment 方法中传递片段实例。创建与底部导航侦听器项 id 对应的所需数量的片段实例。

          Fragment fragmentA;
          protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_home);
          
          fragmentA = new Fragment();
          updateFragment(fragmentA);
          }
          
          public void updateFragment(Fragment fragment) {
          FragmentTransaction transaction = 
          getSupportFragmentManager().beginTransaction();
          transaction.add(R.id.layoutFragment, fragment);
          transaction.commit();
          }
          

          此外,请确保您使用的是 android.support.v4.app.Fragment 并调用 getSupportFragmentManager()

          【讨论】:

          • OP 说他们使用的是Navigation Component。使用 Navigation 时不会直接执行 FragmentTransactions。
          • 是的,我明白,但 OP 只想切换标签。我想这一定是一个更好的方法。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2021-02-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多