Android 的沉浸式状态栏


文章目录

  • Android 的沉浸式状态栏
  • 前言
  • 一、 什么是沉浸式状态栏?
  • 二、沉浸式状态栏的实现阶段
  • 2.1.Android4.4 - Android 5.0 (API 19-21):
  • 2.2.Android 5.0(API 21)以上版本:
  • 2.3. Android 6.0(API 23)以上版本:
  • 三、setSystemUiVisibility 对于沉浸式的具体影响
  • 3.1 View.SYSTEM_UI_FLAG_FULLSCREEN (隐藏状态栏)
  • 3.2View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
  • 3.3. 同时设置 SYSTEM_UI_FLAG_FULLSCREEN 和SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
  • 3.4. View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
  • 3.5View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
  • 3.6. 同时设置View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
  • 3.7 View.SYSTEM_UI_FLAG_LAYOUT_STABL
  • 3.8 View.SYSTEM_UI_FLAG_IMMERSIVE
  • 3.9 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY粘性的沉浸式 帮助app实现真正的"全屏"效果
  • 3.10. View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
  • 3.11. View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
  • 4. 常用的实现沉浸式状态栏的方式
  • 5. 在不同的情景下使用沉浸式
  • 5.1 在Fragment里使用沉浸式状态栏
  • 5.2.在Dialog里使用沉浸式
  • 6.获取状态栏高度
  • 7. 刘海屏对沉浸式状态栏的影响及刘海屏适配
  • 7.1 什么是刘海屏
  • 7.2 刘海屏的优势和劣势
  • 7.3 Android P 中Google对刘海屏的适配
  • 7.4 我们对刘海屏的适配方法
  • 7.5 主流国产手机刘海屏高度的适配



前言

作为一名佛系的Android 开发者,沉浸式状态栏自入坑以来一直是令人感到麻烦的问题,做做总结,希望可以帮到大家

一、 什么是沉浸式状态栏?

“沉浸式状态栏”准确来说应该是“透明栏”,英文名“Translucent Bars”,是安卓 4.4 新定义的设计规范。简单来说就是在软件打开的时候通知栏和软件顶部颜色融为一体,这样可以使软件和系统本身更加融为一体,同时通知栏的颜色不再是白色、黑色简单的两种了。
在支持“沉浸式状态栏”的产品中,通知栏并不像是独立存在,仿佛只是在全屏模式加了几个图标,打开软件第一眼看到的不再是黑乎乎的通知栏,更能将注意力集中到产品内容本身。

二、沉浸式状态栏的实现阶段

2.1.Android4.4 - Android 5.0 (API 19-21):

在这个阶段,Google并没有给我们提供修改状态栏颜色的方法,只有曲线救国,
实现方式为: 通过FLAG_TRANSLUCENT_STATUS设置状态栏为透明并且为全屏模式,然后通过添加一个与StatusBar 一样大小的View,将View 的 background 设置为我们想要的颜色,从而来实现沉浸式。

代码如下(示例):

private void setStatusBarView(int statusBarColor) {
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);

    ViewGroup mDecorView = (ViewGroup) getWindow().getDecorView();
    View statusBarView = mDecorView.findViewById(R.id.my_status_bar_view);
    if (statusBarView == null) {
        statusBarView = new View(mActivity);
        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
                ScreenUtils.getStatusBarHeight());
        params.gravity = Gravity.TOP;
        statusBarView.setLayoutParams(params);
        statusBarView.setVisibility(View.VISIBLE);
        statusBarView.setId(R.id.my_status_bar_view);
        mDecorView.addView(statusBarView);
    }
    statusBarView.setBackgroundColor(statusBarColor);

}

同样的,如果我们的手机支持导航栏,我们也可以通过设置FLAG_TRANSLUCENT_NAVIGATION 实现透明导航栏,然后通过添加一个与NavigationBar 一样大小的View,将View 的 background 设置为我们想要的颜色,从而来实现导航栏的沉浸式。

当然,因为现在几乎没有年轻人用这么低版本的手机了,这一阶段的沉浸式适配是可以说了解就好,不必深究。

2.2.Android 5.0(API 21)以上版本:

Android 5.0 是一个里程碑式的版本,从这个版本开始,google 加入了一个比较重要的方法setStatusBarColor , 通过这个方法,可以很轻松地实现沉浸式状态栏,想要这个方法生效,必须还要配合一个Flag一起使用,必须设置FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS ,并且不能设置FLAG_TRANSLUCENT_STATUS(Android 4.4才用这个)
解释:设置了FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,表明会Window负责系统bar的background 绘制,绘制透明背景的系统bar(状态栏和导航栏),
然后用getStatusBarColor()和getNavigationBarColor()的颜色填充相应的区域。这就是Android 5.0 以上实现沉浸式导航栏的主要原理。

示例实现代码及效果如下:(示例):

public void setStatusBarColor(int statusBarColor) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
        getWindow().setStatusBarColor(statusBarColor);
    }
}

android 有没状态栏 安卓状态栏_android 有没状态栏


可以看到,我们使用setStatusBarColor(Color.parseColor("#FF018786"));后将状态栏变为了绿色。

同样的,我们也可以设置导航栏的颜色

示例实现代码及效果如下:(示例):

public void setNavigationBarColor(int navigationBarColor) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
        getWindow().setNavigationBarColor(navigationBarColor);
    }
}

android 有没状态栏 安卓状态栏_UI_02


可以看到,当我们调用setNavigationBarColor(Color.parseColor("#FF018786"));以后,导航栏变成了绿色。

2.3. Android 6.0(API 23)以上版本:

从Android 6.0(API 23)开始,我们可以改状态栏的绘制模式,可以显示白色或浅黑色的内容和图标(除了魅族手机,魅族自家有做源码更改,需要单独适配)
我们在后面说到setSystemUiVisibility时会具体的讲到。

三、setSystemUiVisibility 对于沉浸式的具体影响

在Android 4.1 (API level 16) 及以上的系统上,你可以通过使setSystemUiVisibility() 设置UI flags;这些设置会影响状态栏和导航栏的效果。使用setSystemUiVisibility() 来设置UI flags比起使用 WindowManager flags能更细致的控制系统栏的显示

我们一般如下设置:

View decorView = getWindow().getDecorView();
decorView.setSystemUiVisibility(UI flags);

要注意的是:一旦UI flags被清除了,你的app需要重新设置这些flags。

为了更明显的展示状态栏和导航栏的变化,我们设定一个布局

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white"

    >

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_red_dark"
        android:gravity="center"
        android:paddingBottom="40dp"
        android:text="顶部文字"
        android:textColor="@color/white"
        android:textSize="20sp"
        app:layout_constraintTop_toTopOf="parent"
        />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_red_dark"
        android:gravity="center"
        android:paddingTop="40dp"
        android:text="底部文字"
        android:textColor="@color/white"
        android:textSize="20sp"
        app:layout_constraintBottom_toBottomOf="parent"
        />
</androidx.constraintlayout.widget.ConstraintLayout>

我们在内容布局的最上和最下方分别添加一个红色背景的TextView,以方便我们查看SYSTEM_UI_FLAG变化对我们内容布局的影响。如下所示

android 有没状态栏 安卓状态栏_android 有没状态栏_03

3.1 View.SYSTEM_UI_FLAG_FULLSCREEN (隐藏状态栏)

作用:隐藏StatusBar

和WindowManager.LayoutParams.FLAG_FULLSCREEN 有相同的效果,此Flag会因为各种的交互而被系统清除。

单独使用效果:状态栏隐藏,但原状态栏占据的屏幕位置仍被占据,内容布局不能侵入到该位置,下拉状态栏或启动新的页面等一些操作会使状态栏重新出现

效果如下:

android 有没状态栏 安卓状态栏_android 有没状态栏_04

3.2View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN

作用:在不隐藏StatusBar的情况下,将内容布局显示范围扩展到StatusBar下面。因此Activity的部分内容也因此被StatusBar覆盖遮挡。
当使用此Flag时,在根布局设置fitSystemWindow=true,会被系统自动添加大小为statusBar和ActionBar高度之和相同的paddingTop。
单独使用时的效果:(下面第一张没有添加fitSystemWindow,第二张设置了fitSystemWindow=true)可以看到第一张顶部的文本框侵入了状态栏的位置导致显示不全,第二张我们在内容布局ConstraintLayout的属性中加入了android:fitsSystemWindows=“true”,系统自动给ConstraintLayout添加了状态栏高度的paddingTop,布局没有被遮挡

android 有没状态栏 安卓状态栏_UI_05


android 有没状态栏 安卓状态栏_状态栏_06

3.3. 同时设置 SYSTEM_UI_FLAG_FULLSCREEN 和SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN

当我们同时设置这两个属性的时候,我们发现会同时具有两种FLAG的特性。

SYSTEM_UI_FLAG_FULLSCREEN使状态栏隐藏,SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN让我们的布局侵入到了状态栏的范围内,但当我们在状态栏位置下滑或跳转其他页面等操作的时候状态栏又重新显示,覆盖了我们的布局

如下图所示,我们从状态栏位置下滑状态栏会重新出现覆盖住文字,这里不再展示图片

android 有没状态栏 安卓状态栏_android 有没状态栏_07

3.4. View.SYSTEM_UI_FLAG_HIDE_NAVIGATION

作用:隐藏系统NavigationBar,和StatusBar的隐藏不同,NavigationBar的隐藏除了隐藏NavigationBar的内容,还会隐藏NavigationBar占据的屏幕位置
但是用户的任何交互(包括最简单的触摸屏幕),都会导致此Flag被系统清除,进而导航栏自动重新显示,如果我们设置了View.SYSTEM_UI_FLAG_FULLSCREEN那么同时该Flag也会被自动清除,因此StatusBar也会同时显示出来。 但此时导航栏并不会遮挡底部的布局,因为导航栏重新显示布局调整,造成了布局闪动的感觉。

3.5View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION

作用:在不隐藏导航栏的情况下,将Activity的显示范围扩展到导航栏底部。同时Activity的部分内容也因此被NavigationBar覆盖遮挡。

当使用此Flag时,设置fitSystemWindow=true的view,会被系统自动添加大小为NavigationBar高度相同的paddingBottom。

如下图:第一张是单独使用该Flag并且没有添加fitSystemWindow=true的时候,我们看到内容布局侵入到了导航栏下导致布局被遮挡,第二张我们在内容布局ConstraintLayout的属性中加入了android:fitsSystemWindows=“true”,系统自动给ConstraintLayout添加了导航栏高度的paddingBottom,布局没有被遮挡

android 有没状态栏 安卓状态栏_UI_08


android 有没状态栏 安卓状态栏_UI_09

3.6. 同时设置View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION View.SYSTEM_UI_FLAG_HIDE_NAVIGATION

我们发现效果具有了两者的特点,导航栏隐藏,触摸屏幕导航栏出现,但布局又侵入了导航栏,从而导致遮挡了布局。

3.7 View.SYSTEM_UI_FLAG_LAYOUT_STABL

作用:稳定布局。我们在动态显示隐藏状态栏和导航栏的时候,内容的布局有时候会发生变动,在我们看来就是布局闪了一下,这是不友好的
该Flag的作用主要提现在两个方面:
1.当我们设置 View.SYSTEM_UI_FLAG_HIDE_NAVIGATION和
View.SYSTEM_UI_FLAG_FULLSCREEN,会隐藏状态栏和导航栏的同时,不会隐藏占据的空间,只会变成空白。
2.当使用SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN或SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 时,如果我们同时设置了fitSystemWindow=true,系统会为此View自动设置padding。
此时使用SYSTEM_UI_FLAG_LAYOUT_STABLE的作用便是当状态栏和导航栏动态显示隐藏,系统设置的padding都不会变化,因此布局内容的位置不会变化,从而不会引起布局的闪动。

3.8 View.SYSTEM_UI_FLAG_IMMERSIVE

作用:避免某些用户交互造成系统自动清除全屏状态。 帮助app实现真正的"全屏"效果

View.SYSTEM_UI_FLAG_IMMERSIVE和View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY的使用主要是为了当设置全屏模式时,避免某些用户交互造成系统自动清除全屏状态。
我们在使用前面的Flag时,会出现触摸屏幕就会使状态栏和导航栏重新出现的场景,但我们是不希望这样的,所以这两个Flag会配合使用
,只有(通过在系统栏正常显示的位置向内滑动、Window的变化比如页面跳转等)会导致导航栏的隐藏状态被系统自动清除;否则,任何交互都会导致导航栏的隐藏状态被系统自动清除。

3.9 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY粘性的沉浸式 帮助app实现真正的"全屏"效果

作用:避免某些用户交互造成系统自动清除全屏状态。
我们在使用前面的Flag时,会出现触摸屏幕就会使状态栏和导航栏重新出现的场景,但我们是不希望这样的,View.SYSTEM_UI_FLAG_IMMERSIVE 允许配合使用,通过在系统栏正常显示的位置向内滑动、Window的变化比如页面跳转等 特殊情况下重新调起状态栏和导航栏,但这并不方便

该Flag则会 当我们在系统栏的区域中向内滑动会引起系统栏出现一个半透明的状态, flags 不会被清除,系统栏会在几秒收或者点击屏幕后消失。

android 有没状态栏 安卓状态栏_android 有没状态栏_10


是不是看着很亲切,喜欢玩游戏的小伙伴对这种透明的一会自动小时的状态栏一定最熟悉不过了,它给了我们最好的全屏体验。

3.10. View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR

作用:android 6.0以上设置状态栏字体图标为暗色
Android 系统状态栏的字色和图标颜色为白色,当我的主题色或者图片接近白色或者为浅色的时候,状态栏上的内容就看不清了。为了解决这个问题,增加了View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR 来实现暗色的字体图标
如下图:系统默认状态栏是白色字体和图标,当我们设置了这个Flag以后会变为暗色

android 有没状态栏 安卓状态栏_android 有没状态栏_11


android 有没状态栏 安卓状态栏_UI_12

3.11. View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR

作用:设置导航栏图标为暗色
效果不再展示

4. 常用的实现沉浸式状态栏的方式

  1. 我们不调用setSystemUiVisibility方法修改系统栏的状态,这种情况下,状态栏和导航栏都存在,我们可以直接设置setNavigationBarColor和setNavigationBarColor修改系统里为我们喜欢的颜色。这种方法的缺点是我们无法实现图片复杂背景的沉浸式。
  2. 我们设置View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN并添加android:fitsSystemWindows="true"让系统给我们添加padding解决布局与状态栏重叠问题,再设置setNavigationBarColor和setNavigationBarColor修改系统里为我们喜欢的颜色。这种方法的缺点和第一种是一样的。
  3. 我们添加View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN,让布局侵入到状态栏,然后我们将状态栏设置为透明的,然后就可以随意设置。为了不让状态栏与布局重叠,我们需要自己添加padding,这个padding的大小就是我们获取的状态栏的高度。

5. 在不同的情景下使用沉浸式

5.1 在Fragment里使用沉浸式状态栏

Fragment依附于Activity,Fragment使用沉浸式其实也就是Activity使用沉浸式。
第一种方式:在Activity里调用沉浸式方法
Fragment两种使用方法,第一种在ViewPager里面使用时,我们添加ViewPager.OnPageChangeListener监听ViewPager的切换,当切换到某一个Fragment时,调用沉浸式方法。第二种是通过show()、hide()方法显示的Fragment,我们在FragmentTransaction.commit()提交事务后,根据会显示哪个Fragment调用沉浸式方法。

第二种方式:在Fragment里调用沉浸式方法
Fragment两种使用方法,第一种在ViewPager里面使用时,我们就需要监听setUserVisibleHint来判断到底对用户是否可见。第二种是通过show()、hide()方法显示的Fragment,这时候我们需要监听onHiddenChanged方法来判断到底对用户是否可见。 这两种方式我们都要监听Fragment是否可见,当可见时调用沉浸式方法。

5.2.在Dialog里使用沉浸式

Dialog有自己的Window,因此拿到Dialog的window,用Dialog的window来设置沉浸式,如果设置沉浸式的时候使用了setSystemUiVisibility,我们知道window的变化会影响setSystemUiVisibility设置的Flag,所以我们需要对Dialog设置setOnDismissListener来监听Dialog的消失,重新设置Dialog所在Activity的沉浸式样式

6.获取状态栏高度

状态栏高度定义在系统尺寸资源中status_bar_height,但这并不是公开可直接使用的,我们需要 用系统给我们提供的Resource类,通过这个类我们可以获取资源文件。
示例代码如下:

private int getStatusBarHeight(Context context) {
    int statusBarHeight = 0;
    try {
        int resourceId = Resources.getSystem().getIdentifier("status_bar_height", "dimen", "android");
        if (resourceId > 0) {
            statusBarHeight = context.getResources().getDimensionPixelSize(resourceId);
            return statusBarHeight;
        }
    } catch (Resources.NotFoundException ignored) {
        return 0;
    }
    return statusBarHeight;
}

基本上所有的国产手机都可以通过这个方法拿到状态栏高度,但不排除个别无良商家,作者确实遇到过有手机拿不到状态栏高度导致状态栏与布局重合,虽然只有一两次,但还是建议大家在状态栏高度是0的时候给一个默认值

7. 刘海屏对沉浸式状态栏的影响及刘海屏适配

7.1 什么是刘海屏

屏幕某处(一般是正上方居中位置)会有一块黑色区域,该黑色区域无法正常显示内容(百度百科说的好听一些就是追求极致边框而采用的一种手机解决方案),因为很像小姑娘的刘海而得名。又因为像是屏幕挖掉的一个孔,也会给叫做挖孔屏、异形屏、凹凸屏
刘海屏要归功于苹果公司,2017年9月,正式发布的苹果iPhone X,其特殊的前脸设计一度让不少消费者感到不适应,尤其是强迫症患者——这样的设计被戏称为“刘海屏”。有用户表示:如果乔布斯还在,这种事情绝对不会发生。在2018年,不管是中国厂商还是外国厂商,大家都在跟风做刘海屏手机。

7.2 刘海屏的优势和劣势

刘海屏的优势:
可以避免出现下巴,最大程度对提高手机屏占比,刘海屏小小的区域里可以放置前置摄像头以及传感器,这也是大多数国产手机选择刘海屏的原因。

刘海屏的劣势:
“微微”有点“丑”,微微是谁咱也不知道

7.3 Android P 中Google对刘海屏的适配

获取刘海屏的信息:

google在Android P中的提供了对刘海屏的适配
通过 DisplayCutout 类,可以确定刘海区域的位置和形状

DisplayCutout 类获取刘海的方法:
1.getBoundingRects() 返回刘海区域的列表
2.getSafeInsetLeft () 返回刘海区域距离屏幕左边的距离
可以理解为横屏情况下左边的刘海
3.getSafeInsetRight () 返回刘海区域距离屏幕右边的距离
可以理解为横屏情况下右边的刘海
4.getSafeInsetTop () 返回刘海区域距离屏幕顶部的距离
可以理解为竖屏情况下顶边的刘海
5.getSafeInsetBottom() 返回刘海区域距离屏幕底部的距离
可以理解为竖屏情况下底边的刘海

示例代码如下:

private void getNotchInfo() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
        View decorView = getWindow().getDecorView();
        WindowInsets windowInsets = decorView.getRootWindowInsets();
        DisplayCutout displayCutout = windowInsets.getDisplayCutout();
        Log.e("刘海区域SafeInsetLeft", displayCutout.getSafeInsetLeft() + "");
        Log.e("刘海区域SafeInsetRight", displayCutout.getSafeInsetRight() + "");
        Log.e("刘海区域SafeInsetTop", displayCutout.getSafeInsetTop() + "");
        Log.e("刘海区域SafeInsetBottom", displayCutout.getSafeInsetBottom() + "");

        List<Rect> boundingRects = displayCutout.getBoundingRects();
        if (boundingRects == null || boundingRects.size() == 0) {
            Log.e("不是刘海屏", "---------");
        } else {
            Log.e("刘海数量:", boundingRects.size() + "");
            for (int i = 0; i < boundingRects.size(); i++) {
                Log.e("刘海区域:", i + "-" + boundingRects);
            }
        }
    }
}

作者的手机是小米10,在屏幕顶部左侧有一小块刘海区域,主要用来放置前置摄像头,我们打印看一看刘海屏信息:

android 有没状态栏 安卓状态栏_UI_13


当然我们可以开启开发者模式,模拟不同的刘海屏样式:

android 有没状态栏 安卓状态栏_状态栏_14


当我们切换到双刘海屏时候,打印信息如下:

android 有没状态栏 安卓状态栏_android_15


未适配刘海屏对沉浸式状态栏的影响:

1.非全屏的页面(状态栏存在并显示):对于刘海屏一般规定刘海区域高度小于状态栏区域,也就是状态栏包含刘海区域,这种情况下几乎没有影响

如下图所示:(小米10 顶部左侧有一小块刘海区域用来放置前置摄像头,截屏看不到。)

android 有没状态栏 安卓状态栏_android 有没状态栏_16


2.对于全屏的页面:默认情况下,布局会做下移处理,避开刘海区显示,这时候刘海区域变成一条黑边。

如下图所示,在添加

getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);

请求全屏后,并没有实现真正的全屏,刘海区域变成了一条黑边

android 有没状态栏 安卓状态栏_UI_17


Google Android P 对刘海屏的适配:

Android P中新增了一个布局参数属性layoutInDisplayCutoutMode,包含了三种不同的模式
设置代码如下:

private void fitsNotchScreen(Activity activity) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
        Window mWindow = activity.getWindow();
        WindowManager.LayoutParams lp = mWindow.getAttributes();
        lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
        mWindow.setAttributes(lp);
    }
}

1.AYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
默认情况下,全屏窗口不会使用到刘海区域,非全屏窗口可正常使用刘海区域,这里的使用其实也就是刘海区域占据状态栏的区域。
效果图参照上一小节,这里不再展示

2.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER

窗口决不允许与刘海区域重叠。比第一种更强硬,即时我们没有全屏状态栏已然存在的情况下,布局也会做下移处理,避开刘海区显示,这时候刘海区域变成一条黑边。

效果如下:在没有添加任何全屏效果的情况下,设置该Flag,依旧会出现黑边

android 有没状态栏 安卓状态栏_android 有没状态栏_18


3. LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES

始终允许布局延伸到的刘海区域。

不设置全屏只设置该Flag:

android 有没状态栏 安卓状态栏_UI_19


我们同时设置全屏代码和该Flag,

getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);

效果如下:(左侧有一块刘海区域)

android 有没状态栏 安卓状态栏_UI_20


可以看到黑边消失了,我们的布局入侵到了刘海和状态栏的区域内。

7.4 我们对刘海屏的适配方法

设置LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES,非全屏情况下无需操作;全屏情况下,状态栏消失,但刘海区域还在,如果我们的布局会被刘海区域遮挡住,就需要做适当的位移防止遮挡。一般我们会给顶部的布局增加padding,这个padding就是状态栏和刘海区域高度最大的一个。

判断有无刘海区域和获取刘海区域高度:

Android P以后判断有无刘海屏和刘海屏的高度

private static DisplayCutout getDisplayCutout(Activity activity) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
        Window window = activity.getWindow();
        WindowInsets windowInsets = window.getDecorView().getRootWindowInsets();
        if (windowInsets != null) {
            return windowInsets.getDisplayCutout();
        }
    }
    return null;
}

private static boolean hasNotchAndroidP(Activity activity) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
        DisplayCutout displayCutout = getDisplayCutout(activity);
        if (displayCutout == null) {
            return false;
        }
        List<Rect> boundingRects = displayCutout.getBoundingRects();
        if (boundingRects == null || boundingRects.size() == 0) {
            return false;
        } else {
            return true;
        }
    }
    return false;
}
private static int getNotchHeight(Activity activity) {
    int notchHeight = 0;
    int statusBarHeight = getStatusBarHeight(activity);
    DisplayCutout displayCutout = getDisplayCutout(activity);

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && displayCutout != null) {
        notchHeight = displayCutout.getSafeInsetTop();
    }
    if (notchHeight < statusBarHeight) {
        notchHeight = statusBarHeight;
    }
    return notchHeight;
}

7.5 主流国产手机刘海屏高度的适配

虽然Google在Android P增加了对刘海屏的适配,但国内的手机厂商们在Android P之前就发布了刘海屏的手机,而且各家的特点都不一致,具体的可以查阅官方文档。因此我们也需要对国内主流手机的刘海屏跟据官方文档处理一下。

判断刘海和获取刘海高度的代码来自网络上的整理,感谢大佬们的付出。

小米:小米手机判断有无刘海屏和获取刘海屏高度的方法:

private static boolean hasNotchXiaoMi(Context context) {
    int result = 0;
    if ("Xiaomi".equals(Build.MANUFACTURER)) {
        try {
            ClassLoader classLoader = context.getClassLoader();
            @SuppressLint("PrivateApi")
            Class<?> aClass = classLoader.loadClass("android.os.SystemProperties");
            Method method = aClass.getMethod("getInt", String.class, int.class);
            result = (Integer) method.invoke(aClass, "ro.miui.notch", 0);

        } catch (NoSuchMethodException ignored) {
        } catch (IllegalAccessException ignored) {
        } catch (InvocationTargetException ignored) {
        } catch (ClassNotFoundException ignored) {
        }
    }
    return result == 1;
}

private static int getNotchHeightXiaoMi(Context context) {
    int resourceId = context.getResources().getIdentifier("notch_height", "dimen", "android");
    if (resourceId > 0) {
        return context.getResources().getDimensionPixelSize(resourceId);
    } else {
        return 0;
    }
}

华为: 华为手机判断有无刘海屏和获取刘海屏高度的方法:

private static boolean hasNotchHuaWei(Context context) {
    boolean result = false;
    try {
        ClassLoader classLoader = context.getClassLoader();
        Class<?> aClass = classLoader.loadClass("com.huawei.android.util.HwNotchSizeUtil");
        Method get = aClass.getMethod("hasNotchInScreen");
        result = (boolean) get.invoke(aClass);
    } catch (ClassNotFoundException ignored) {
    } catch (NoSuchMethodException ignored) {
    } catch (Exception ignored) {
    }
    return result;
}

private static int[] geNotchSizetHuaWei(Context context) {
    int[] ret = new int[]{0, 0};
    try {
        ClassLoader cl = context.getClassLoader();
        Class<?> aClass = cl.loadClass("com.huawei.android.util.HwNotchSizeUtil");
        Method get = aClass.getMethod("getNotchSize");
        return (int[]) get.invoke(aClass);
    } catch (ClassNotFoundException ignored) {
        return ret;
    } catch (NoSuchMethodException ignored) {
        return ret;
    } catch (Exception ignored) {
        return ret;
    }
}

ViVO:

vivo判断是否有刘海屏通过反射获取

private static boolean hasNotchVIVO(Context context) {
    boolean result = false;
    try {
        ClassLoader classLoader = context.getClassLoader();
        @SuppressLint("PrivateApi")
        Class<?> aClass = classLoader.loadClass("android.util.FtFeature");
        Method method = aClass.getMethod("isFeatureSupport", int.class);
        result = (boolean) method.invoke(aClass, 0x00000020);
    } catch (ClassNotFoundException ignored) {
    } catch (NoSuchMethodException ignored) {
    } catch (Exception ignored) {
    }
    return result;
}

private static int getNotchHeightVIVO(Activity activity) {
    int notchHeight = dp2px(activity, 32);
    int statusBarHeight = getStatusBarHeight(activity);

    if (notchHeight < statusBarHeight) {
        notchHeight = statusBarHeight;
    }
    return notchHeight;
}

OPPO:OPPO手机判断有无刘海屏和获取刘海屏高度的方法:

private static boolean hasNotchOPPO(Context context) {
    try {
        return context.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism");
    } catch (Exception ignored) {
        return false;
    }
}
private static int getNotchHeightOPPO(Activity activity) {
    int notchHeight = 80;
    int statusBarHeight = getStatusBarHeight(activity);
    if (notchHeight < statusBarHeight) {
        notchHeight = statusBarHeight;
    }
    return notchHeight;
}

刘海屏的适配内容其实是很多的,这里只讲了一点,因为国产手机厂商的任性,让我们的适配工作变得花里胡哨的,这就是Android 的程序员。