注册

Android — DialogFragment显示后隐藏的导航栏显示问题

沉浸式显示广泛的应用于大部分App中,基本上可以说是App的必备功能,在之前的文章Android 全屏显示和沉浸式显示中介绍过如何通过WindowInsetsControllerCompat实现沉浸式显示。当然,我也将其应用到了我司的App中。但是在后续开发测试的过程中发现了一个奇怪的现象,加载弹窗显示时,已经隐藏的底部导航栏又显示出来了。


问题复现


下面通过一段示例代码演示一下:



  • 加载弹窗

class LoadingDialogFragment : DialogFragment() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NORMAL, R.style.LoadingDialog)
}

override fun onCreateView(inflater: LayoutInflater, container: ViewGr0up?, savedInstanceState: Bundle?): View {
dialog?.let { containerDialog ->
containerDialog.window?.run {
setBackgroundDrawable(ContextCompat.getDrawable(requireContext(), android.R.color.transparent))
decorView.setBackgroundResource(android.R.color.transparent)
val layoutParams = attributes
layoutParams.width = DensityUtil.dp2Px(200)
layoutParams.height = DensityUtil.dp2Px(120)
layoutParams.gravity = Gravity.CENTER
attributes = layoutParams
}
containerDialog.setCancelable(true)
containerDialog.setCanceledOnTouchOutside(false)
}
return LayoutLoadingDialogBinding.inflate(layoutInflater, container, false).root
}
}


  • 示例页面

class DialogFragmentExampleActivity : AppCompatActivity() {

val DIALOG_TYPE_LOADING = "loadingDialog"

private lateinit var insetsController: WindowInsetsControllerCompat

private var alreadyChanged = false

private var callDismissDialogTime = 0L

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = LayoutDialogFragmentExampleActivityBinding.inflate(layoutInflater).also {
setContentView(it.root)
}

// 调整系统栏
WindowCompat.setDecorFitsSystemWindows(window, false)
insetsController = WindowCompat.getInsetsController(window, window.decorView).also {
it.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
it.hide(WindowInsetsCompat.Type.navigationBars())
}
window.statusBarColor = ContextCompat.getColor(this, android.R.color.transparent)
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, windowInsets ->
if (!alreadyChanged) {
alreadyChanged = true
windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).run {
binding.topView.updateLayoutParams<ConstraintLayout.LayoutParams> { height += top }
binding.tvTitle.updateLayoutParams<ConstraintLayout.LayoutParams> { topMargin += top }
}
}
WindowInsetsCompat.CONSUMED
}

binding.btnShowLoadingDialog.setOnClickListener {
showLoadingDialog()
}
}

private fun showLoadingDialog() {
LoadingDialogFragment().run {
show(supportFragmentManager, DIALOG_TYPE_LOADING)
}
// 模拟耗时操作,两秒后关闭弹窗
lifecycleScope.launch(Dispatchers.IO) {
delay(2000)
dismissLoadingDialog()
}
}

private fun dismissLoadingDialog() {
callDismissDialogTime = System.currentTimeMillis()
lifecycleScope.launch(Dispatchers.IO) {
if (async { checkLoadingDialogStatue() }.await()) {
withContext(Dispatchers.Main) {
// 从supportFragmentManager中获取加载弹窗,并调用隐藏方法
(supportFragmentManager.findFragmentByTag(DIALOG_TYPE_LOADING) as? DialogFragment)?.run {
if (dialog?.isShowing == true) {
dismissAllowingStateLoss()
}
}
}
}
}
}

/**
* 检查加载弹窗的状态直到获取到加载弹窗或者超过时间
*/

private suspend fun checkLoadingDialogStatue(): Boolean {
return if (supportFragmentManager.findFragmentByTag(DIALOG_TYPE_LOADING) == null && System.currentTimeMillis() - callDismissDialogTime < 1500L) {
delay(100)
checkLoadingDialogStatue()
} else {
true
}
}
}

效果如图:


Screen_recording_202 -big-original.gif

解决显示异常问题


上述示例代码中,在示例页面的初始化方法中通过WindowInsetsControllerCompat对页面的WindowdecorView进行操作,隐藏了导航栏。但是在DialogFragment中,Dialog对象也有其所属的WindowdecorView,上述示例代码中并没有针对Dialog所属的WindowdecorView进行配置。


基于上面的分析,对示例代码进行调整,调整如下:



  • 加载弹窗

class LoadingDialogFragment : DialogFragment() {

......

override fun onCreateView(inflater: LayoutInflater, container: ViewGr0up?, savedInstanceState: Bundle?): View {
dialog?.let { containerDialog ->
containerDialog.window?.run {
WindowCompat.setDecorFitsSystemWindows(this, false)
WindowCompat.getInsetsController(this, decorView).also {
it.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
it.hide(WindowInsetsCompat.Type.navigationBars())
}
......
}
......
}
return LayoutLoadingDialogBinding.inflate(layoutInflater, container, false).root
}
}

修改后效果如图:


fix.gif

适配不同机型


通常来说,上述示例代码用的是官方的API,应该不会出现什么意外,然而还是出现了意外。公司的另一台三星的测试机跟我自己的测试机Pixel 3a XL效果差别很大。


三星测试机(SM-A515F)效果如下:


未调整调整后
Screen_recording_202 -big-original.gifScreen_recording_202 -big-original.gif

虽然这可能是安卓的通病,但对于这种情况我还是感到有些遗憾,通用API在不同厂商的手机上效果居然差这么多。虽然遗憾,但还是得解决问题。


根据效果图来看,对页面的配置生效了,对Dialog的配置也生效了,但是DialogFragment隐藏后重置了对页面的配置。最简单的处理就是在DialogFragment消失之后判断下导航栏是否显示,显示则隐藏。


调整代码如下:



  • 加载弹窗

class LoadingDialogFragment : DialogFragment() {

......

override fun onDestroyView() {
super.onDestroyView()
// 这里通过setFragmentResult API 来传递弹窗已经关闭的消息。
parentFragmentManager.setFragmentResult(DialogFragmentExampleActivity::class.java.simpleName, Bundle())
}
}


  • 示例页面

class DialogFragmentExampleActivity : AppCompatActivity() {

......

private var navigationBarShow = false

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = LayoutDialogFragmentExampleActivityBinding.inflate(layoutInflater).also {
setContentView(it.root)
}

supportFragmentManager.setFragmentResultListener(this::class.java.simpleName, this) { requestKey, result ->
// 接收加载弹窗关闭的消息
if (requestKey == this::class.java.simpleName) {
if (navigationBarShow) {
// 根据实践,不延迟500毫秒有概率出现无法隐藏的情况。
lifecycleScope.launch(Dispatchers.IO) {
delay(500L)
withContext(Dispatchers.Main) {
hideNavigationBar()
}
}
}
}
}

......

ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, windowInsets ->
windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).run {
if (!alreadyChanged) {
alreadyChanged = true
binding.topView.updateLayoutParams<ConstraintLayout.LayoutParams> { height += top }
binding.tvTitle.updateLayoutParams<ConstraintLayout.LayoutParams> { topMargin += top }
}
// 当底部空间不为0时可以判断导航栏显示
navigationBarShow = bottom != 0
}
WindowInsetsCompat.CONSUMED
}

......
}

......

private fun hideNavigationBar() {
insetsController.hide(WindowInsetsCompat.Type.navigationBars())
}

override fun onDestroy() {
super.onDestroy()
// 页面销毁时清除监听
supportFragmentManager.clearFragmentResultListener(this::class.java.simpleName)
}
}

修改后效果如图:


Screen_recording_202 -big-original.gif

示例


演示代码已在示例Demo中添加。


ExampleDemo github


ExampleDemo gitee


作者:ChenYhong
来源:juejin.cn/post/7313742254145208356

0 个评论

要回复文章请先登录注册