Android适配刘海屏沉浸式状态栏的一些坑

在国内做Android开发真的不容易,国内的深度定制“安卓”总能时不时的给你来几个“惊喜”。

起因

18年简直是刘海元年,所有手机都在跟风刘海屏,甚至每个厂商还有自己的一套适配规范。我的初始需求很简单,就是做一个全屏显示的页面,一般情况下只需要开启Android规范的全屏模式就好:

<item name="android:windowFullscreen">true</item>

结果,在真机上测试发现系统为了适配不遮挡内容,默认全屏模式为非刘海屏,就是刘海那栏直接填黑,严重影响观感,这明显不符合我的需求。因此走上了适配刘海的一条不归路。

方案一

经过调试,发现普通模式下(非全屏)是可以将内容正常显示到整个屏幕的,这时候最顶部显示的就是应用默认的 StatusBar,由于状态栏的颜色默认为colorPrimaryDark,也就是说需要手动指定的,而我需要的打倒的效果为沉浸式显示,状态栏颜色需要和内容一致,甚至将内容扩充显示到状态栏上,因此我想到了以下:

<item name="android:windowTranslucentStatus">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>

第一个参数将状态栏透明化,这时候我们的布局内容就会自动扩充到状态栏上,然后再使用第二个参数将状态栏颜色设置成透明,最后在每个布局中动态添加一个高度等于状态栏的自定义view进行占位,搞定。

刘海屏手机上测试,完美,再换回普通手机,GG。发现虽然将状态栏设置成了透明,但是依然存在一个半透明的遮罩,强迫症的我显然不能忍,方案一失败。

方案二

为了解决上述操作下状态栏始终会存在一个半透明的遮罩,进行了一系列调研和尝试,最后发现另外一套方案:

<item name="android:windowTranslucentStatus">false</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:windowTranslucentNavigation">true</item>

和第一种相比,将状态栏透明化关闭,将导航透明化打开。重点是android:windowTranslucentNavigation这个参数,他的作用和android:windowTranslucentStatus类似,可以让内容布局扩充出去,而且影响的不仅仅是StatusBar,还有NavigationBar也就是底部的虚拟按键。

使用这种设置之后,状态栏的半透明遮罩总算是去掉了,但是也带来了一个弊端,那就是内容会填充到NavigationBar上面,也就是说如果用户手机开启了虚拟按键的话,虚拟按键会悬浮在视图之上,这样很容易带来误操作,因此我们需要像之前一样在底部也加入一个高度等于虚拟按键的view进行占位。

这里大概说下统一为布局添加占位view的方法,创建BaseFullScreenActivity重写ActivitysetContentView()方法,在方法中获取到我们设置的布局,将其提取出来,并且重新创建一个LinearLayout,依次装入head、content、foot。核心代码如下:

    override fun setContentView(layoutResID: Int) {
        super.setContentView(layoutResID)
        findViewById<ViewGroup>(android.R.id.content).let {
            val contentGroup = LinearLayout(this).apply {
                layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.MATCH_PARENT)
                orientation = LinearLayout.VERTICAL
            }
            it.setOnApplyWindowInsetsListener { view, windowInsets ->
                view.dispatchApplyWindowInsets(windowInsets)
            }
            //customView
            it.getChildAt(0).apply {
                val lp = LinearLayout.LayoutParams(layoutParams)
                lp.height = 0
                lp.weight = 1f
                layoutParams = lp
                it.removeAllViews()
                contentGroup.addView(this)
            }
            //navigationBar
            contentGroup.addView(View(this).apply {
                layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                        getBottomStatusHeight(this@BaseFullScreenActivity))
                setBackgroundColor(Color.BLACK)
            })
            it.addView(contentGroup)
        }
    }

红米6上测试,完美,普通手机上测试,完美。就在我觉得终于搞定了的时候,突然有一天,公司换了一波新测试机。而其中就有刘海屏的小米8。我本着吃饱了没事做的作死精神第一时间对小米8进行了测试。

结果状态栏没问题,可是底部出现了一条黑边,无独有偶,我在小米8上使用GeekBench准备跑分的时候发现同样也出现了黑边。最后测试发现,因为该方案需要手动计算底部虚拟按键高度进行填充,而在小米8上面,无论是否存在虚拟按键,计算结果都是存在虚拟按键高度的!这里真心要吐槽一下MIUI的工程师,什么鬼!

最终方案

最终方案很容易,集以上两种方案的优点,却没有第二种的缺点。设置也一样简单,问我为什么不一开始用这个,因为我不知道啊。直接上配置:

<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:windowTranslucentStatus">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>

这里相比方案一多了个设置:android:windowDrawsSystemBarBackgrounds,设置了该方法之后,状态栏上的半透明遮罩直接就消失了。可以说很方便了,可惜不知道,查了很久也没结果。

MIUI10 Bug

通常我们获取当前屏幕高度方法为下:

    val wm = activity.getSystemService(Service.WINDOW_SERVICE) as WindowManager
    val windowSize = Point()
    wm.defaultDisplay.getSize(windowSize)

但是在小米8上,无论是否开启虚拟按键,得到的屏幕高度都是存在虚拟按键的高度。不知道是否为故意设置的安全区域,总之在使用过程中已经发现了好几款市面上的三方App受到该bug影响导致显示异常

写在最后

这里的三个方案可以对应不同的使用场景,通过这些坑也让我对StatusBarNavigationBar有了深入的了解。还有就是小米的那个bug,真的无解,而且会影响到一些会用到屏幕尺寸的计算,只能后续再看看了。

新的方案

 override fun onCreate(savedInstanceState: Bundle?) {
    // 自动沉浸
    enableEdgeToEdge()
    // 导航键兼容
	ViewCompat.setOnApplyWindowInsetsListener(fab) { v, windowInsets ->
	  val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
	  // Apply the insets as a margin to the view. This solution sets
	  // only the bottom, left, and right dimensions, but you can apply whichever
	  // insets are appropriate to your layout. You can also update the view padding
	  // if that's more appropriate.
	  v.updateLayoutParams<MarginLayoutParams>(
	      leftMargin = insets.left,
	      bottomMargin = insets.bottom,
	      rightMargin = insets.right,
	  )
	
	  // Return CONSUMED if you don't want want the window insets to keep passing
	  // down to descendant views.
	  WindowInsetsCompat.CONSUMED
	}
	
	// 隐藏导航键
	val windowInsetsController =
      WindowCompat.getWindowInsetsController(window, window.decorView)
	windowInsetsController.hide(Type.systemBars())

	// 设置状态栏主题色
	windowInsetsController.isAppearanceLightNavigationBars = false
    windowInsetsController.isAppearanceLightStatusBars = false
    super.onCreate(savedInstanceState)
    ...
  }

参考文献 https://developer.android.com/develop/ui/views/layout/edge-to-edge

  • 12
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
Unity沉浸状态栏是指在使用Unity引擎开发应用程序时,可以隐藏或者自定义应用程序的状态栏。在移动设备上,状态栏通常包含手机的信号、电池信息等系统图标。通过实现沉浸状态栏,开发者可以使应用程序在全屏状态下运行,更好地提供用户体验。 在Unity中,实现沉浸状态栏可以通过以下步骤进行: 首先,开发者需要在Unity中创建一个全屏的视图,包括一整块可以占满幕的画布。 然后,通过调用Unity的API函数,可以隐藏状态栏。在Android平台上,可以使用Application类的方法SetStatusBarHidden()将状态栏隐藏起来,并且设置为全屏状态。在iOS平台上,可以使用Screen类的方法SetStatusBarHidden()来隐藏状态栏。 此外,也可以自定义状态栏的样,使其与应用程序的设计风格相符。例如,在Android平台上,可以使用Unity提供的插件设置状态栏的颜色、字体颜色等属性。 需要注意的是,沉浸状态栏功能在不同的平台上具有不同的实现方法和限制。为了确保应用程序的兼容性和稳定性,在使用沉浸状态栏的时候,开发者需要根据不同的平台和设备进行适配和测试。 综上所述,Unity沉浸状态栏是通过隐藏或自定义状态栏,在应用程序运行时提供更加全屏的体验。开发者可以通过Unity的API函数实现沉浸状态栏,并根据需要进行定制,提升应用程序的用户体验。
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值