一、适配策略
- 非全屏状态下不需要适配刘海屏,刘海只占据 toolbar 的位置。
- 全屏时需要适配刘海屏
二、使用系统提供的 CutoutModel
CutoutModel 有三种模式。
**1.LAYOUY_IN_DISPLAY_CUTOUT_MODEL_DEFAULT 全屏模式下内容下移,非全屏不受影响 **
**1.LAYOUY_IN_DISPLAY_CUTOUT_MODEL_SHORT_EDGES 允许内容区延伸进刘海区 **
**1.LAYOUY_IN_DISPLAY_CUTOUT_MODEL_NEVER 不允许内容区延伸进刘海区 **
//2.让内容区域延伸进刘海
WindowManager.LayoutParams params = window.getAttributes();
/**
* * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT 全屏模式,内容下移,非全屏不受影响
* * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES 允许内容去延伸进刘海区
* * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER 不允许内容延伸进刘海区
*/
params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
window.setAttributes(params);
三、设置沉浸式布局
沉浸式的布局才可以让自己的 View 延伸到状态栏中。如果不启用沉浸式的布局顶部会是黑边。
int flags = View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
int visibility = window.getDecorView().getSystemUiVisibility();
visibility |= flags; //追加沉浸式设置
window.getDecorView().setSystemUiVisibility(visibility);
四、判断是否有刘海
判断是否有刘海可以从是否具备刘海 刘海的高度不为0 版本在9.0以上这几个方面考虑
private boolean hasDisplayCutout(Window window) {
DisplayCutout displayCutout;
View rootView = window.getDecorView();
WindowInsets insets = rootView.getRootWindowInsets();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && insets != null){
displayCutout = insets.getDisplayCutout();
//
if (displayCutout != null){
// 判断有几个刘海,刘海数不为 0 且 刘海的高度大于 0
if (displayCutout.getBoundingRects() != null && displayCutout.getBoundingRects().size() > 0 && displayCutout.getSafeInsetTop() > 0){
return true;
}
}
}
return false;
}
七、View 在刘海处的解决方案
1.让这个 View 下移刘海的高度
2.设置父容器的上部分的 Padding 为刘海的高度
Button button = findViewById(R.id.button);
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) button.getLayoutParams();
layoutParams.topMargin = heightForDisplayCutout();
button.setLayoutParams(layoutParams);
RelativeLayout layout = findViewById(R.id.container);
layout.setPadding(layout.getPaddingLeft(), heightForDisplayCutout(), layout.getPaddingRight(), layout.getPaddingBottom());
六、总结
刘海屏的适配的流程是
1.是否有刘海(需要考虑华为,OPPO,小米等厂商的定制)
2.进行全屏设置
3.沉浸式设置
4.如果有 View 在刘海处,单独考虑
public class DisplayCutoutActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//1.设置全屏
requestWindowFeature(Window.FEATURE_NO_TITLE);
Window window = getWindow();
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
//华为, 小米,oppo
//1.判断手机厂商, 2,判断手机是否刘海, 3,设置是否让内容区域延伸进刘海 4,设置控件是否避开刘海区域 5, 获取刘海的高度
//判断手机是否是刘海屏
boolean hasDisplayCutout = hasDisplayCutout(window);
if (hasDisplayCutout){
//2.让内容区域延伸进刘海
WindowManager.LayoutParams params = window.getAttributes();
/**
* * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT 全屏模式,内容下移,非全屏不受影响
* * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES 允许内容去延伸进刘海区
* * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER 不允许内容延伸进刘海区
*/
params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
window.setAttributes(params);
//3.设置成沉浸式 这样才能真的让自己 View 的内容进入刘海区,否则只能
int flags = View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
int visibility = window.getDecorView().getSystemUiVisibility();
visibility |= flags; //追加沉浸式设置
window.getDecorView().setSystemUiVisibility(visibility);
}
setContentView(R.layout.activity_main);
// 如果某一个控件在刘海位置,可以获取刘海的高度,让它下降这部分高度
// Button button = findViewById(R.id.button);
// RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) button.getLayoutParams();
// layoutParams.topMargin = heightForDisplayCutout();
// button.setLayoutParams(layoutParams);
// 设置 Padding 让布局向下移动
RelativeLayout layout = findViewById(R.id.container);
layout.setPadding(layout.getPaddingLeft(), heightForDisplayCutout(), layout.getPaddingRight(), layout.getPaddingBottom());
}
private boolean hasDisplayCutout(Window window) {
DisplayCutout displayCutout;
View rootView = window.getDecorView();
WindowInsets insets = rootView.getRootWindowInsets();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && insets != null){
displayCutout = insets.getDisplayCutout();
//
if (displayCutout != null){
// 判断有几个刘海,刘海数不为 0 且 刘海的高度大于 0
if (displayCutout.getBoundingRects() != null && displayCutout.getBoundingRects().size() > 0 && displayCutout.getSafeInsetTop() > 0){
return true;
}
}
}
return true; //因为模拟器原因,这里设置成true
}
//通常情况下,刘海的高就是状态栏的高
// 也可以使用 displayCutout.getSafeInsetTop() 来设置
public int heightForDisplayCutout(){
int resID = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resID > 0){
return getResources().getDimensionPixelSize(resID);
}
return 96;
}
}
华为小米OPPO手机是否有刘海
public class Utils {
/**
* 华为是否有刘海
* @param context
* @return
*/
public static boolean hasNotchInScreen(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 (ClassNotFoundException e) {
Log.e("test", "hasNotchInScreen ClassNotFoundException");
} catch (NoSuchMethodException e) {
Log.e("test", "hasNotchInScreen NoSuchMethodException");
} catch (Exception e) {
Log.e("test", "hasNotchInScreen Exception");
}
return ret;
}
/**
* 获取刘海尺寸:width、height,int[0]值为刘海宽度 int[1]值为刘海高度。
* @param context
* @return
*/
public static int[] getNotchSize(Context context) {
int[] ret = new int[]{0, 0};
try {
ClassLoader cl = context.getClassLoader();
Class HwNotchSizeUtil = cl.loadClass("com.huawei.android.util.HwNotchSizeUtil");
Method get = HwNotchSizeUtil.getMethod("getNotchSize");
ret = (int[]) get.invoke(HwNotchSizeUtil);
} catch (ClassNotFoundException e) {
Log.e("test", "getNotchSize ClassNotFoundException");
} catch (NoSuchMethodException e) {
Log.e("test", "getNotchSize NoSuchMethodException");
} catch (Exception e) {
Log.e("test", "getNotchSize Exception");
}
return ret;
}
/**
* 设置使用刘海区域
* @param window
*/
public static void setFullScreenWindowLayoutInDisplayCutout(Window window) {
if (window == null) {
return;
}
try {
WindowManager.LayoutParams layoutParams = window.getAttributes();
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");
}
}
/*刘海屏全屏显示FLAG*/
public static final int FLAG_NOTCH_SUPPORT = 0x00010000;
/**
* 设置应用窗口在华为刘海屏手机不使用刘海
*
* @param window 应用页面window对象
*/
public static void setNotFullScreenWindowLayoutInDisplayCutout(Window window) {
if (window == null) {
return;
}
try {
WindowManager.LayoutParams layoutParams = window.getAttributes();
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("clearHwFlags", int.class);
method.invoke(layoutParamsExObj, FLAG_NOTCH_SUPPORT);
} catch (Exception e) {
Log.e("test", "hw clear notch screen flag api error");
}
}
/*********
* 1、声明全屏显示。
*
* 2、适配沉浸式状态栏,避免状态栏部分显示应用具体内容。
*
* 3、如果应用可横排显示,避免应用两侧的重要内容被遮挡。
*/
/********************
* 判断该 OPPO 手机是否为刘海屏手机
* @param context
* @return
*/
public static boolean hasNotchInOppo(Context context) {
return context.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism");
}
/**
* 刘海高度和状态栏的高度是一致的
* @param context
* @return
*/
public static int getStatusBarHeight(Context context) {
int resId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resId > 0){
return context.getResources().getDimensionPixelSize(resId);
}
return 0;
}
/**
* Vivo判断是否有刘海, Vivo的刘海高度小于等于状态栏高度
*/
public static final int VIVO_NOTCH = 0x00000020;//是否有刘海
public static final int VIVO_FILLET = 0x00000008;//是否有圆角
public static boolean hasNotchAtVivo(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 (ClassNotFoundException e) {
Log.e("Notch", "hasNotchAtVivo ClassNotFoundException");
} catch (NoSuchMethodException e) {
Log.e("Notch", "hasNotchAtVivo NoSuchMethodException");
} catch (Exception e) {
Log.e("Notch", "hasNotchAtVivo Exception");
} finally {
return ret;
}
}
}