萌工厂

  • 首页
  • 小软件
  • 关于
生之为萌,乐享创造
  1. 首页
  2. Android
  3. 正文

电表 × Android:解决 Fragment 显示重叠

2020年7月24日 0条评论

佛系更新。

现在几乎所有 Android 应用都有一个 Activity 对应多个 Fragment 的架构,因此如何解决 Activity 和 Fragment 的各种爱恨情仇和实现正确的状态管理就是一个比较烦人的问题了。

什么问题

电表的主 Activity 中包含概览、发现和我三个 Fragment。在最开始,我其实没有太在意状态管理的事情,因为一般情况下除非是有异常没有捕捉到,否则是不会触发 Fragment 重叠的问题的(于是那时候我写了大量的 try-catch 来避免异常不被捕捉,俗话说得好,解决不了问题就解决问题提出问题的人)。

但是,在 2.4.0 版本中加入夜间模式让我不得不开始正视这个问题。根据 Google 官方文档,主题切换会触发 uiMode 配置的更改,导致所有已经加载的 Activity 重启。这个时候 Fragment 重叠就必须解决了。

分析问题

首先我们来了解一下 Activity 和 Fragment 的生命周期以及相互对应:

当然这个图并不是那么完整。前面提到的 Google 官方文档中指出,主题更改会触发 Activity 的 onConfigurationChanged()。之后,还会执行 onPause()、onSaveInstanceState()、onStop() 和 onDestroy()。相应地,在这个过程中,依附于该 Activity 的所有 Fragment 也会销毁重建。而 Activity 销毁前,处于可见状态的最后一个 Fragment 会在这一系列操作完成之后默认显示。当通过 Bottom Navigation Bar 切换 Fragment 时,其他 Fragment 会被创建并显示。同时,由于一般我们会选择创建一个全局变量来指示当前显示的是哪一个 Fragment,而此时在 Activity 重建之后这个变量是默认值,因此就会导致重叠。另外,如果没有做相关处理的话,这个时候 Bottom Navigation Bar 和显示出来的 Fragment 应该也不是对应的。

解决问题

既然知道问题出在哪里,那么解决起来就比较方便了。下面说一下电表里曾经用过的,和现在正在使用的办法。

简单粗暴:强制不进行状态管理

上面提到了 onSaveInstanceState() 方法,默认情况下 Activity 会在该方法中保存相关状态。默认情况下,如果在 Activity 中按下 Ctrl + O 选择重写该方法,那么你会得到以下代码:

override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)
}

这里调用了基类的同名方法,自动完成相关状态的保存。至于具体机制和原理,不是我们这里讨论的重点。

这么看来,既然保存了还出问题,那就干脆不保存了。注释掉 super.onSaveInstanceState(outState),使得这个方法的方法体为空。这样就不会保存 Activity 持有的 Fragment 的状态了。于是在 Activity 重启之后,会默认显示第一个 Fragment,同时完成生命周期的加载部分。

优雅一点:老老实实折腾

禁止 Activity 保存状态虽然不会导致出现 Fragment 重叠的问题,但是这样也就会丢失原本 Fragment 的各种信息,用户体验也就相应大打折扣。那咋办啊?折腾呗。

Activity 中

前面提到了在 Activity 中有一个变量指示当前显示的是哪一个 Fragment,这里我们假定名字是 currentFragmentIndex。

首先在 Activity 中重写 onSaveInstanceState() 方法,注意这里不要删掉原有的 super.onSaveInstanceState(outState) 语句。在方法中我们需要保存 currentFragmentIndex 变量的值。像这样:

override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)
    outState.putInt("current_frag", currentFragmentIndex)
}

然后我们需要改一改添加 Fragment 的地方。一般情况下我们会使用 ft.add(R.id.fragment_container, fragment) 来添加将 Fragment 添加到 Activity 的 FragmentManager 中。现在修改为 ft.add(R.id.fragment_container, fragment, TAG),其中 TAG 对于每个 Fragment 需要时独一无二的。添加TAG是为了恢复时能找到之前保存的 Fragment。

最后,在 Activity 初始化的时候,需要对 savedInstanceState 进行判空。如果为空的话则说明 Activity 是正常加载的,进行相关初始化即可。如果非空的话说明是恢复的,此时需要从 FragmentManager 中取出保存的 Fragment。如下所示:

supportFragmentManager.findFragmentByTag("summary")
        ?.let { fragments.add(it as SummaryFragment) }
        ?: run { fragments.add(summaryFragment) }

此时就要用到上面的 TAG 了。这里判空是为了在遇到没有找到保存的 Fragment 时可以使用新创建的 Fragment。

Fragment

一般来说,Fragment 中如果没有什么特别的要求,可以不用进行额外操作。不过在电表中,概览界面的一卡通余额、校园网流量和图书馆借阅信息这些,布局的 onClickListener 的有无是有一个状态控制的,在 Fragment 重建之后,当然希望保留这些,于是同样可以重写 onSaveInstanceState() 方法:

override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)
    outState.putBoolean("card_summary_loaded", mBinding.layoutCardBalance.hasOnClickListeners())
    outState.putBoolean("network_summary_loaded", mBinding.layoutNetworkBalance.hasOnClickListeners())
    outState.putBoolean("library_summary_loaded", mBinding.layoutLibrary.hasOnClickListeners())
}

然后同样,在 Fragment 初始化的时候对 savedInstanceState 判空,再进行相关操作即可。

View Model

如果使用了 Android Architecture Components 来构建 MVVM 架构的 app,那么可以将相关信息保存在 Activity 或者 Fragment 对应的 View Model 中,交由其自动进行管理。这样就不需要手动在 onSaveInstanceState 方法中进行保存了。不过这么做有点不太恰当,一般来说,还是更建议将 UI 相关的数据保存到 savedInstanceState 中。

电表
2020年10月12日
上一篇
下一篇

文章评论

取消回复

Robotxm

爱做白日梦,偶尔犯二

标签聚合
日记 翻唱 旅行 多图杀猫 总结 面基 电表 Windows 10
快捷方式
  • Blog-YXR
  • Frank
  • icebound-area
  • Shadow Forest
  • 佳佳酱
  • 烙饼日志
  • 神楽坂ニャン
  • 腹黑猫の猫窝
  • 薫風の匂い
  • 雨の言葉

Copyright © 2011-2021 MoeFactory All Rights Reserved

Some illustrations by Arseniy Chebynkin

THEME KRATOS MADE BY VTROIS, MODS CREATED BY ROBOTXM