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

2018-12-14 17:15:50

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


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


起因

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


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

1

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


方案一

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


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

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

1

2

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


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


方案二

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


<item name="android:windowTranslucentStatus">false</item>

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

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

1

2

3

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


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


这里大概说下统一为布局添加占位view的方法,创建BaseFullScreenActivity重写Activity的setContentView()方法,在方法中获取到我们设置的布局,将其提取出来,并且重新创建一个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)

        }

    }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

红米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>

1

2

3

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


MIUI10 Bug

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


    val wm = activity.getSystemService(Service.WINDOW_SERVICE) as WindowManager

    val windowSize = Point()

    wm.defaultDisplay.getSize(windowSize)

1

2

3

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


写在最后

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

--------------------- 

作者:落叶Ex 

来源:CSDN 

原文:https://blog.csdn.net/ccw0054/article/details/82498691 

版权声明:本文为博主原创文章,转载请附上博文链接!


  • 2020-11-22 23:04:39

    Android组件 LiveData与MutableLiveData教程

    LiveData与ViewMode是经常搭配在一起使用的,但是为了不太混乱,我还是拆分开来说明,此篇博客只讲解 LiveData 与 MutableLiveData的概念与使用方式(但是会涉及到ViewMode的部分代码).

  • 2020-11-22 23:14:52

    Dagger 2 在 Android 上的用法

    在前面的文章我们介绍了Dagger2 中的大部分注解的使用,接下来我们从源码角度分析下第一篇文章中例子的原理。

  • 2020-11-22 23:18:59

    Android开发从Dagger2迁移至Kodein的感受

    最近个人在尝试构建 Kotlin版本 的Android MVVM开发框架,在依赖注入框架的选型上,我最终选择了 Kodein 。这是一个非常轻量级的DI框架,相比于配置繁琐的Dagger(繁琐的配置也是导致Dagger学习成本一直居高不下的原因!),它的配置过程更清晰且简单,并且,这个库的源码也是 Kotlin 的。

  • 2020-11-22 23:25:56

    Dagger2源码解析inject过程

    添加inject后,通过编译生成的DaggerMainComponent类来导入,说明编译以后生成了一些类,那到底生成了什么类呢。 Module和Component又是什么,该怎么里理解 在这篇文章后里将一一讨论。

  • 2020-11-22 23:27:28

    dagger学习教程

    dagger android 学习(一):dagger基础使用 dagger android 学习(二):AndroidInjector的使用 dagger android 学习(三):ContributesAndroidInjector的进一步优化 dagger android 学习(四):基于dagger2的mvp架构

  • 2020-11-22 23:31:22

    Dagger2与AndroidInjector详解

    相信使用过Dagger开发Android应用的小伙伴会知道(如果你还不是很了解Daager,可以先看我之前的一篇基本介绍:Dagger2使用攻略),我们会在Activity或Fragment的生命周期方法中执行成员注入。比如这样:

  • 2020-11-23 08:52:59

    asm.js 和 Emscripten 入门教程

    asm.js 就是为了解决这两个问题而设计的:它的变量一律都是静态类型,并且取消垃圾回收机制。除了这两点,它与 JavaScript 并无差异,也就是说,asm.js 是 JavaScript 的一个严格的子集,只能使用后者的一部分语法。

  • 2020-11-23 09:11:07

    爬虫——记一次破解前端加密详细过程

    从最初使用webdriver+selenium爬虫到现在利用http请求解析html,经历过各种各样的问题,webdriver+selenium这种办法虽然万能,而且可以用JS写解析脚本方便调试,

  • 2020-11-24 19:18:43

    nuxtjs打成用于webview的相对路径

    路径为绝对路径,当项目的域名为二级域名的时候,就不能打包为这绝对路径了。 nuxt不同于vue项目,思索了许久,终于找到了配置的地方