前言

目前市面上的刘海屏和水滴屏手机越来越多了,颜值方面是因人而异,有的人觉得很好看,也有人觉得丑爆了,我个人觉得是还可以。但是作为移动开发者来说,这并不是一件好事,越来越多异形屏手机的出现意味着我们需要投入大量精力在适配上(就不提之后会出的折叠屏手机了)。本文总结了当下主流手机的刘海屏适配方案,鉴于目前Android碎片化的情况,想要覆盖所有的机型是不可能的,但是能适配一些是一些,总比什么都不做要好。

所谓刘海屏,指的是手机屏幕正上方由于追求极致边框而采用的一种手机解决方案。因形似刘海儿而得名——来自百度百科,水滴屏也是类似,为了简单起见,下文就统称这两种为刘海屏了。




android DialogFragment适配刘海屏 android 刘海屏 全屏_全屏显示


什么时候需要适配

这里先上一张官方的图


android DialogFragment适配刘海屏 android 刘海屏 全屏_全屏显示_02


从图中可以看出,刘海区域是镶嵌在状态栏内部的,刘海区域的高度一般是不超过状态栏高度的。因此,当我们的应用布局需要占据状态栏来显示时,就需要考虑到刘海区域是否会遮挡住页面上的控件或者背景,这就是为什么将状态栏区域称为危险区域。如果应用不需要占据状态栏显示,全部显示在安全区域内,那么恭喜你,不需要做任何适配处理。总结来说,只有当应用需要全屏显示时才需要进行适配。

全屏显示无非就是两种情况:第一种是我们常说的沉浸式状态栏,也就是状态栏透明,页面的布局延伸到状态栏显示,这种情况下状态栏依然可见;第二种是类似应用的闪屏页风格,页面全屏显示,状态栏不可见。这两种情况下如果不进行适配处理都会产生一些问题。

先来看第一种情况,沉浸式风格。需要将状态栏设置为透明,需要注意只有在Android 4.4(API Level 19)以上才支持设置透明状态栏。有两种设置方法:

方法一:为Activity设置style,添加一个属性:

true

方法二:在Activity的onCreate()中为Window添加Flag

public class ImmersiveActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_immersive); // 透明状态栏 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { getWindow().addFlags( WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); } }}

页面的布局很简单,只包含一个按钮,为了明显,我为根布局设置了一个背景。

activity_immersive.xml

<?xml version="1.0" encoding="utf-8"?>

运行之后发现按钮会被刘海区域所遮挡,如图所示:


android DialogFragment适配刘海屏 android 刘海屏 全屏_xml_03


再说第二种情况,全屏风格,状态栏不可见。同样有两种设置方法:

方法一:为Activity设置style,添加属性:

true@mipmap/bg

方法二:在Activity的OnCreate()中添加代码:

public class FullScreenActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 全屏显示 getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); }}

补充说明一点,现在的手机屏幕高宽比例越来越大,我们还需要额外做一下适配才能使应用在所有手机上都能全屏显示,具体方式有两种:

方式一:在AndroidManifest.xml中配置支持最大高宽比

或者

android:maxAspectRatio="ratio_float" (API LEVEL 26)

说明:以上两种接口可以二选一,ratio_float = 屏幕高 / 屏幕宽 (如oppo新机型屏幕分辨率为2280 x 1080, ratio_float = 2280 / 1080 = 2.11,建议设置 ratio_float为2.2或者更大)

方式二:在AndroidManifest.xml中配置支持分屏,注意验证分屏下界面兼容性

android:resizeableActivity="true"

也可以通过设置targetSdkVersion>=24(即Android 7.0),该属性的值会默认为true,就不需要在AndroidManifest.xml中配置了。

运行之后,我们发现状态栏的部分留出了一条黑边,看上起很奇怪,这显然不是我们想要的效果。


android DialogFragment适配刘海屏 android 刘海屏 全屏_状态栏_04


如何适配

上文中已经展示了刘海屏中全屏显示带来的问题,那么如何去解决呢?

1、沉浸式状态栏的适配

其实沉浸式状态栏带来的遮挡问题与刘海屏无关,本质上是由于设置了透明状态栏导致布局延伸到了状态栏中,就算是不具有刘海屏,一定程度上也会造成布局的遮挡。不过既然刘海屏是处在状态栏当中的,那么我们就把这种情况也包含在刘海屏的适配中。清楚了原因之后,解决起来就很简单了,我们只需要让控件或布局避开状态栏显示就可以了,具体的解决方法有三种。

方法一、利用fitsSystemWindows属性

当我们给最外层View设置了android:fitsSystemWindows="true"属性后,当设置了透明状态栏或者透明导航栏后,就会自动给View添加paddingTop或paddingBottom属性,这样就在屏幕上预留出了状态栏的高度,我们的布局就不会占用状态栏来显示了。

activity_immersive.xml

<?xml version="1.0" encoding="utf-8"?>

方法二、根据状态栏高度手动设置paddingTop

这种方法的实现本质上和设置fitsSystemWindows是一样的,首先获取状态栏高度,然后设置根布局的paddingTop等于状态栏高度就可以了,代码如下:

public class ImmersiveActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_immersive); // 透明状态栏 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { getWindow().addFlags( WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); } LinearLayout llRoot = findViewById(R.id.ll_root); // 设置根布局的paddingTop llRoot.setPadding(0, getStatusBarHeight(this), 0, 0); } /** * 获取状态栏高度 * * @param context * @return */ public int getStatusBarHeight(Context context) { int statusBarHeight = 0; int resourceId = context.getResources().getIdentifier("status_bar_height