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

佛系更新。

现在几乎所有 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 中。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇