适配刘海屏的两种情况
1.沉浸式状态栏:适配方案就是将窗口布局下移,预留出状态栏的空间。
2.全屏显示模式:不做适配的话状态栏会呈现一条黑边。适配方案是首先判断系统版本,是Android P及以上就按照官方的API来适配,否则根据手机厂商的适配方案进行适配
1、 针对沉浸式状态栏3种方案适配--可以成功的避开状态栏(危险区域)
//方法一:利用fitsSystemWindows属性
当我们给最外层View设置了android:fitsSystemWindows="true"属性后,当设置了透明状态栏或者透明导航栏后,就会自动给View添加paddingTop或paddingBottom属性,
这样就在屏幕上预留出了状态栏的高度,我们的布局就不会占用状态栏来显示了。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ll_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@mipmap/bg"
android:fitsSystemWindows="true"
android:orientation="vertical">
<Button
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" />
</LinearLayout>
//方法二:根据状态栏高度手动设置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);
}
/**
* 获取状态栏高度
*/
public int getStatusBarHeight(Context context) {
int statusBarHeight = 0;
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
statusBarHeight = context.getResources().getDimensionPixelSize(resourceId);
}
return statusBarHeight;
}
}
//方法三:在布局中添加一个和状态栏高度相同的View
这里在根布局中添加了一个透明的View,高度和状态栏高度相同。这种方法的好处是可以自定义填充状态栏View的背景,更灵活地实现我们想要的效果
public class ImmersiveActivity extends AppCompatActivity {
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);
View statusBarView = new View(this);
statusBarView.setBackgroundColor(Color.TRANSPARENT);
ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
getStatusBarHeight(this));
// 在根布局中添加一个状态栏高度的View
llRoot.addView(statusBarView, 0, lp);
}
/**
* 获取状态栏高度
*/
public int getStatusBarHeight(Context context) {
int statusBarHeight = 0;
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
statusBarHeight = context.getResources().getDimensionPixelSize(resourceId);
}
return statusBarHeight;
}
}
2、针对全屏显示的适配
2.1、Android P及以上
谷歌官方从Android P开始给开发者提供了刘海屏相关的API,通过DisplayCutout类可以获得安全区域的范围以及刘海区域的信息,需要注意只有API Level在28及以上才可以调用。
/**
* 获得刘海区域信息
*/
@TargetApi(28)
public void getNotchParams() {
final View decorView = getWindow().getDecorView();
if (decorView != null) {
decorView.post(new Runnable() {
@Override
public void run() {
WindowInsets windowInsets = decorView.getRootWindowInsets();
if (windowInsets != null) {
// 当全屏顶部显示黑边时,getDisplayCutout()返回为null
DisplayCutout displayCutout = windowInsets.getDisplayCutout();
Log.e("TAG", "安全区域距离屏幕左边的距离 SafeInsetLeft:" + displayCutout.getSafeInsetLeft());
Log.e("TAG", "安全区域距离屏幕右部的距离 SafeInsetRight:" + displayCutout.getSafeInsetRight());
Log.e("TAG", "安全区域距离屏幕顶部的距离 SafeInsetTop:" + displayCutout.getSafeInsetTop());
Log.e("TAG", "安全区域距离屏幕底部的距离 SafeInsetBottom:" + displayCutout.getSafeInsetBottom());
// 获得刘海区域
List<Rect> rects = displayCutout.getBoundingRects();
if (rects == null || rects.size() == 0) {
Log.e("TAG", "不是刘海屏");
} else {
Log.e("TAG", "刘海屏数量:" + rects.size());
for (Rect rect : rects) {
Log.e("TAG", "刘海屏区域:" + rect);
}
}
}
}
});
}
}
如果是在style中设置了全屏模式,在适配之前,顶部状态栏区域显示一条黑边,在适配之前无法通过DisplayCutout判断是否存在刘海屏。
因此只能对于所有设备都添加适配代码。Android P中增加了一个窗口布局参数属性layoutInDisplayCutoutMode,该属性有三个值可以取:
1、LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT:默认的布局模式,如果没有设置为全屏显示模式,就允许窗口延伸到刘海区域,否则不允许。
2、LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER:永远不允许窗口延伸到刘海区域。
3、LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES:始终允许窗口延伸到屏幕短边上的刘海区域
public class FullScreenActivity extends AppCompatActivity {
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
WindowManager.LayoutParams lp = getWindow().getAttributes();
//1、 仅当缺口区域完全包含在状态栏之中时,才允许窗口延伸到刘海区域显示
lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
//2、 永远不允许窗口延伸到刘海区域
lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
//3、 始终允许窗口延伸到屏幕短边上的刘海区域
lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
getWindow().setAttributes(lp);
}
}
}
由结果可以看出第一种和第二种都会有一条黑边,也就是不允许窗口布局延伸到刘海区域,第三种允许窗口布局延伸到刘海区域。因此采用第三种模式。
2.2、Android P以下
针对Android P以下的手机,主要还是针对目前主流的手机品牌,总结了华为、小米、Vivo和Oppo的适配方案。
华为适配方案:
1、判断是否有刘海屏
/**
* @return true:有刘海屏;false:没有刘海屏
*/
public static boolean hasNotch(Context context) {
boolean ret = false;
try {
ClassLoader cl = context.getClassLoader();
Class HwNotchSizeUtil = cl.loadClass("com.huawei.android.util.HwNotchSizeUtil");
Method get = HwNotchSizeUtil.getMethod("hasNotchInScreen");
ret = (boolean) get.invoke(HwNotchSizeUtil);
} catch (Exception e) {
Log.e("test", "hasNotchInScreen Exception");
} finally {
return ret;
}
}
2、设置使用刘海区显示
两种适配方案:
方案一:使用新增的meta-data属性android.notch_support,在应用的AndroidManifest.xml中增加或者在Activity中增加,此属性不仅可以针对Application生效,也可以对Activity配置生效
<meta-data android:name="android.notch_support" android:value="true"/>
方案二:使用给window添加新增的FLAG_NOTCH_SUPPORT
public static void setFullScreenWindowLayoutInDisplayCutout(Window window) {
if (window == null) {
return;
}
WindowManager.LayoutParams layoutParams = window.getAttributes();
try {
Class layoutParamsExCls = Class.forName("com.huawei.android.view.LayoutParamsEx");
Constructor con = layoutParamsExCls.getConstructor(WindowManager.LayoutParams.class);
Object layoutParamsExObj = con.newInstance(layoutParams);
Method method = layoutParamsExCls.getMethod("addHwFlags", int.class);
method.invoke(layoutParamsExObj, FLAG_NOTCH_SUPPORT);
} catch (Exception e) {
Log.e("test", "other Exception");
}
}
小米适配方案
1、判断是否有刘海屏
/**
* @return true:有刘海屏;false:没有刘海屏
*/
public static boolean hasNotch(Context context) {
boolean ret = false;
try {
ClassLoader cl = context.getClassLoader();
Class SystemProperties = cl.loadClass("android.os.SystemProperties");
Method get = SystemProperties.getMethod("getInt", String.class, int.class);
ret = (Integer) get.invoke(SystemProperties, "ro.miui.notch", 0) == 1;
} catch (Exception e) {
e.printStackTrace();
} finally {
return ret;
}
}
2、设置使用刘海区显示
两种适配方案:
方案一:Application级别的控制接口,在 Application 下增加一个 meta-data,用以声明该应用窗口是否可以延伸到状态栏
<meta-data android:name="notch.config" android:value="portrait|landscape"/>
方案二:Window级别的控制接口,通过给Window添加Flag也可以实现将窗口布局延伸到状态栏中显示。
/*刘海屏全屏显示FLAG*/
public static final int FLAG_NOTCH_SUPPORT = 0x00000100; // 开启配置
public static final int FLAG_NOTCH_PORTRAIT = 0x00000200; // 竖屏配置
public static final int FLAG_NOTCH_HORIZONTAL = 0x00000400; // 横屏配置
/**
* 设置应用窗口在刘海屏手机使用刘海区
*/
public static void setFullScreenWindowLayoutInDisplayCutout(Window window) {
// 竖屏绘制到耳朵区
int flag = FLAG_NOTCH_SUPPORT | FLAG_NOTCH_PORTRAIT;
try {
Method method = Window.class.getMethod("addExtraFlags",int.class);
method.invoke(window, flag);
} catch (Exception e) {
Log.e("test", "addExtraFlags not found.");
}
}
Vivo、Oppo适配方案
把Vivo和Oppo放在一起说,官方提供的资料不像华为和小米那么详细,只是提供了判断是否有刘海屏的方法
1、vivo判断是否有刘海屏
public static final int VIVO_NOTCH = 0x00000020; // 是否有刘海
public static final int VIVO_FILLET = 0x00000008; // 是否有圆角
public static boolean hasNotch(Context context) {
boolean ret = false;
try {
ClassLoader classLoader = context.getClassLoader();
Class FtFeature = classLoader.loadClass("android.util.FtFeature");
Method method = FtFeature.getMethod("isFeatureSupport", int.class);
ret = (boolean) method.invoke(FtFeature, VIVO_NOTCH);
} catch (Exception e) {
Log.e("Notch", "hasNotchAtVivo Exception");
} finally {
return ret;
}
}
OPPO判断是否有刘海屏
public static boolean hasNotch(Context context) {
return context.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism");
}