今天说一下android开发中老生常谈的一个问题---屏幕适配。屏幕适配目前来说常用的有4种方案,
- 像素适配(px适配),
- 百分比适配,
- 通过修改系统的density适配,
- 对于谷歌9.0推出的刘海屏,也需要做到刘海屏适配。
我们在开发界面的时候会参考设计师给出的设计稿进行开发,如果设计稿是以px为单位,比如是以512x1920(单位px)的设备为基础,我们可以进行px适配
1、px适配
原理:所谓的px适配就是根据手机系统提供的分辨率,比如1024x1920(单位px)计算得出 宽比为1024/512=2 ,高比为1920/1920=1,根据宽高的缩放比例,实现对子view的等比缩放,即可达到适配的效果,所以大致可分为两步
第一步:计算宽高缩放比例
/**
* <pre>
* author : QB
* time : 2019/3/25
* version : v1.0.0
* qq : 952722763
* email : [email protected]
* desc : 屏幕像素适配工具类
* </pre>
*/
public class ScreenFitUtils {
private static ScreenFitUtils instance = null;
//设计稿给出的像素基准值
private static final float STANDARD_WIDTH = 512;
private static final float STANDARD_HEIGHT = 1920;
//当前手机的真实宽高
private int mCurrentDeviceWidth;
private int mCurrentDeviceHeight;
private ScreenFitUtils(Context mContext) {
//获取屏幕的宽高
if(mCurrentDeviceWidth == 0 || mCurrentDeviceHeight == 0){
WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
if(wm != null){
DisplayMetrics dm = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(dm);
//判断当前是横屏还是竖屏
if(dm.widthPixels > dm.heightPixels){
mCurrentDeviceWidth = dm.heightPixels - getDeviceStatusHeight(mContext);
mCurrentDeviceHeight = dm.widthPixels;
}else{
mCurrentDeviceWidth = dm.widthPixels;
mCurrentDeviceHeight = dm.heightPixels - getDeviceStatusHeight(mContext);
}
}
}
}
/**
* 获取状态栏高度
* @param mContext
*/
private int getDeviceStatusHeight(Context mContext){
int statusID = mContext.getResources().getIdentifier("status_bar_height","dimen","android");
//>0表示获取成功
if(statusID > 0){
return mContext.getResources().getDimensionPixelSize(statusID);
}
return 0;
}
/**
* 单例
* @return
*/
public static ScreenFitUtils getInstance(Context mContext){
if(instance == null){
synchronized (ScreenFitUtils.class){
if(instance == null){
//防止内存泄漏 使用 mContext.getApplicationContext()
instance = new ScreenFitUtils(mContext.getApplicationContext());
}
}
}
return instance;
}
/**
* 获取宽度缩放比例
*/
public float getScaleWidth(){
return mCurrentDeviceWidth/STANDARD_WIDTH;
}
/**
* 获取高度缩放比例
*/
public float getScaleHeight(){
return mCurrentDeviceHeight/STANDARD_HEIGHT;
}
}
首先定义一个工具类,计算出宽度缩放比例和高度缩放比例,如果要实现精确的比例,可以考虑去除状态栏的高度在进行计算。
第二步:自定义RelativeLayout,当然也可以自定义其他类型的ViewGroup容器,实现子view宽高根据缩放比例进行缩放
/**
* <pre>
* author : QB
* time : 2019/3/25
* version : v1.0.0
* qq : 952722763
* email : [email protected]
* desc : 自定义像素适配,实现子view等比缩放
* </pre>
*/
public class RelativePixelsFit extends RelativeLayout {
private boolean isDraw = false;
public RelativePixelsFit(Context context) {
super(context);
}
public RelativePixelsFit(Context context, AttributeSet attrs) {
super(context, attrs);
}
public RelativePixelsFit(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//防止二次测量,scale被放大
if(!isDraw){
isDraw = true;
//获取当前缩放比例
float scaleX = ScreenFitUtils.getInstance(getContext()).getScaleWidth();
float scaleY = ScreenFitUtils.getInstance(getContext()).getScaleHeight();
//对子view进行适配
int childCount = getChildCount();
for (int i=0;i<childCount;i++){
//获取子view
View childView = getChildAt(i);
//获取当前子view布局属性实体
LayoutParams layoutParams = (LayoutParams) childView.getLayoutParams();
layoutParams.width = (int) (layoutParams.width*scaleX);
layoutParams.height = (int) (layoutParams.height*scaleY);
layoutParams.leftMargin = (int) (layoutParams.leftMargin*scaleX);
layoutParams.rightMargin = (int) (layoutParams.rightMargin*scaleX);
layoutParams.topMargin = (int) (layoutParams.topMargin*scaleY);
layoutParams.bottomMargin = (int) (layoutParams.bottomMargin*scaleY);
childView.setLayoutParams(layoutParams);
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
注意代码中,有个防止二次测量的标志位isDraw,不设置这个标志位会使得我们的缩放比例放大。到不到预期的效果。
实现了以上两步基本上就可以尝试一把了,看效果图:可以看到子view的宽度设置成设计稿的一半,就占据了屏幕的一半,达到了适配的效果
2、百分比适配
原理:如果UI想做一个效果,没有给出一定的尺寸参考,只知道宽占据屏幕的一半,高占据屏幕的一半,居中显示,那么就可以使用这种布局方式。当然,android5.0 google 官方也推出了百分比布局支持库,可以参考https://blog.csdn.net/copy_yuan/article/details/52012141文章中做了详细的使用介绍,这里不做说明。这里主要详解自己如何写一个自定义的百分比布局库
第一步:自定义布局属性 谷歌使用的是百分比的格式,这里我们使用float形式
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="PercentLayout">
<attr name="widthPercent" format="float"/>
<attr name="heightPercent" format="float"/>
<attr name="marginLeftPercent" format="float"/>
<attr name="marginRightPercent" format="float"/>
<attr name="marginTopPercent" format="float"/>
<attr name="marginBottomPercent" format="float"/>
</declare-styleable>
</resources>
第二步:自定义RelativeLayout,当然也可以自定义其他类型的ViewGroup容器,实现子view根据百分比进行缩放
/**
* <pre>
* author : QB
* time : 2019/3/26
* version : v1.0.0
* qq : 952722763
* email : [email protected]
* desc : 自定义百分比布局
* </pre>
*/
public class RelativePercentFit extends RelativeLayout {
public RelativePercentFit(Context context) {
super(context);
}
public RelativePercentFit(Context context, AttributeSet attrs) {
super(context, attrs);
}
public RelativePercentFit(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取当前布局的尺寸
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//获取子控件个数
int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
//获取子控件的布局属性
ViewGroup.LayoutParams vl = child.getLayoutParams();
//判断是不是实现了该布局属性
if(checkLayoutParams(vl)){
PercentParams lp = (PercentParams) vl;
float widthPercent = lp.widthPercent;
float heightPercent = lp.heightPercent;
float marginLeftPercent = lp.marginLeftPercent;
float marginRightPercent = lp.marginRightPercent;
float marginTopPercent = lp.marginTopPercent;
float marginBottomPercent = lp.marginBottomPercent;
//做判断
if(widthPercent > 0){
vl.width = (int) (widthPercent * widthSize);
}
if(heightPercent > 0){
vl.height = (int) (heightPercent * heightSize);
}
if(marginLeftPercent > 0){
((PercentParams) vl).leftMargin = (int) (marginLeftPercent * widthSize);
}
if(marginRightPercent > 0){
((PercentParams) vl).rightMargin = (int) (marginRightPercent * widthSize);
}
if(marginTopPercent > 0){
((PercentParams) vl).topMargin = (int) (marginTopPercent * heightSize);
}
if(marginBottomPercent > 0){
((PercentParams) vl).bottomMargin = (int) (marginBottomPercent * heightSize);
}
}
}
}
/**
* 重写对子控件布局属性进行获取解析
*/
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new PercentParams(getContext(),attrs);
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof PercentParams;
}
/**
* 自定义布局属性对象 继承自父亲 保留父亲的布局属性
*/
public static class PercentParams extends RelativeLayout.LayoutParams{
private float widthPercent;
private float heightPercent;
private float marginLeftPercent;
private float marginRightPercent;
private float marginTopPercent;
private float marginBottomPercent;
public PercentParams(Context c, AttributeSet attrs) {
super(c, attrs);
//解析属性值
TypedArray ta = c.obtainStyledAttributes(attrs, R.styleable.PercentLayout);
widthPercent = ta.getFloat(R.styleable.PercentLayout_widthPercent,0);
heightPercent = ta.getFloat(R.styleable.PercentLayout_heightPercent,0);
marginLeftPercent = ta.getFloat(R.styleable.PercentLayout_marginLeftPercent,0);
marginRightPercent = ta.getFloat(R.styleable.PercentLayout_marginRightPercent,0);
marginTopPercent = ta.getFloat(R.styleable.PercentLayout_marginTopPercent,0);
marginBottomPercent = ta.getFloat(R.styleable.PercentLayout_marginBottomPercent,0);
ta.recycle();
}
}
}
通过使用谷歌的依赖库发现,我们使用它提供的自定义布局,系统自带的属性同样可以继续起作用,我们同样要实现这一点,所以这里使用内部类的方式继承了RelativeLayout的布局属性,这样就可以使用原生的属性了,注意generateLayoutParams()方法必须重写,返回我们的内部类对象。可以看出,构造方法中主要是解析自定义属性,onMeasure()方法我们的子view根据属性值重新测量绘制。实现了以上两步基本上就可以尝试一把了,看效果图:
3、density适配
想要实现density适配,我们需要了解到几个概念
- density : 当前设备屏幕总宽度(单位为像素)/ 设计图总宽度(单位为 dp) = density
- scaleDensity:字体缩放比例
- densityDpi:每英寸像素点160个
通过源码发现,无论你在布局文件中是以何种尺寸单位,最终系统都会转成px处理,density的计算方法就是根据源码而来的
public static float applyDimension(int unit, float value,
DisplayMetrics metrics)
{
switch (unit) {
case COMPLEX_UNIT_PX:
return value;
case COMPLEX_UNIT_DIP:
return value * metrics.density;
case COMPLEX_UNIT_SP:
return value * metrics.scaledDensity;
case COMPLEX_UNIT_PT:
return value * metrics.xdpi * (1.0f/72);
case COMPLEX_UNIT_IN:
return value * metrics.xdpi;
case COMPLEX_UNIT_MM:
return value * metrics.xdpi * (1.0f/25.4f);
}
return 0;
}
density在每个设备上都是固定的,DPI / 160 = density ,屏幕的总 px 宽度 / density = 屏幕的总 dp 宽度,以下参考一篇博客,他的例子是比较容易理解的,博客地址https://blog.csdn.net/wy391920778/article/details/81939233
了解到原理,下面我直接上代码
/**
* <pre>
* author : QB
* time : 2019/3/26
* version : v1.0.0
* qq : 952722763
* email : [email protected]
* desc : 修改系统density达到适配
* </pre>
*/
public class DensityUtils {
public static final float WIDTH = 480;//相当于设计稿给出的宽度 dp值
public static float appDensity;//表示屏幕密度
public static float appScaleDensity;//字体缩放比例,默认appDensity
public static void setDensity(final Application application, Activity activity){
//获取app的DisplayMetrics
DisplayMetrics displayMetrics = application.getResources().getDisplayMetrics();
if(appDensity == 0 || appScaleDensity == 0){
appDensity = displayMetrics.density;
appScaleDensity = displayMetrics.scaledDensity;
//添加系统字体变化监听回调
application.registerComponentCallbacks(new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration newConfig) {
//字体大小发生变化条件
if(newConfig != null && newConfig.fontScale > 0){
appScaleDensity = application.getResources().getDisplayMetrics().scaledDensity;
}
}
@Override
public void onLowMemory() {
}
});
}
float targetDensity = displayMetrics.widthPixels/WIDTH;
float targetScaleDensity = targetDensity * (appScaleDensity/appDensity);
int targetDensityDpi = (int) (160*targetDensity);
//替换activity的density scaleDensity densityDpi
DisplayMetrics dm = activity.getResources().getDisplayMetrics();
dm.density = targetDensity;
dm.scaledDensity = targetScaleDensity;
dm.densityDpi = targetDensityDpi;
}
}
注意:代码中为了防止修改系统的字体规格,导致app中显示的字体规格不一致,需要监听系统字体规格的变化。比如,在设置中设置字体为超大,不做监听处理的话,app字体不会跟随变化。效果图如下
4、刘海屏适配
刘海屏适配的几个步骤:
- 判断手机厂商
- 设置成全屏模式
- 判断手机是不是刘海屏
- 让内容区域延伸至刘海屏
- 设置成沉浸式
- 设置控件是否需要避开刘海 ,代码动态修改topMargin,或者父容器设置paddingTop
(1)判断手机厂商
目前很多国内厂商由于定制化的原因,会针对刘海屏适配做不同的处理,参考如下
其他手机厂商(华为,小米,oppo,vivo)适配
华为:https://devcenter-test.huawei.com/consumer/cn/devservice/doc/50114
小米:https://dev.mi.com/console/doc/detail?pId=1293
Oppo:https://open.oppomobile.com/service/message/detail?id=61876
Vivo:https://dev.vivo.com.cn/documentCenter/doc/103
具体不同厂商实现适配代码如下:
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;
}
}
}
(2)设置成全屏模式
//1.设置成全屏
requestWindowFeature(Window.FEATURE_NO_TITLE);
Window widow = getWindow();
widow.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
(3)判断是不是刘海屏,里面做了版本的判断
/**
* 判断是不是刘海屏
* @return
*/
private boolean isHasCutOut(Window widow){
DisplayCutout displayCutout = getDisplayCutout(widow);
if(displayCutout != null){
if(displayCutout.getBoundingRects() != null && displayCutout.getBoundingRects().size()>0 && displayCutout.getSafeInsetTop()>0){
return true;
}
}
return false;
}
private DisplayCutout getDisplayCutout(Window widow){
DisplayCutout displayCutout = null;
//9.0以后才有刘海屏
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P){
View rootView = widow.getDecorView();
WindowInsets rootWindowInsets = rootView.getRootWindowInsets();
if(rootWindowInsets != null){
displayCutout = rootWindowInsets.getDisplayCutout();
}
}
return displayCutout;
}
(4)如果是刘海屏,让内容延伸至刘海区
//2.让内容区域延伸至刘海区
WindowManager.LayoutParams layoutParams = widow.getAttributes();
/**
* * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT 全屏模式,内容下移,非全屏模式不受影响
* * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES 允许内容延伸至刘海
* * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER 不允许内容延伸至刘海
*/
layoutParams.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
widow.setAttributes(layoutParams);
(5)设置成沉浸式
//3.设置成沉浸式
int flag = View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
int visibility = widow.getDecorView().getSystemUiVisibility();
visibility |= flag; // 追加沉浸式设置
widow.getDecorView().setSystemUiVisibility(visibility);
(6)如果想要自己的控件不被刘海区覆盖,需要动态修改控件的布局属性
RelativeLayout rl = findViewById(R.id.rel);
int height = getCutOutHeight();
rl.setPadding(rl.getPaddingLeft(),getCutOutHeight(),rl.getPaddingRight(),rl.getPaddingBottom());
/**
* 通常情况下状态栏的高度可以看成是刘海的高度,刘海的高度<=状态栏高度
* @return
*/
private int getCutOutHeight(){
int resourceId = getResources().getIdentifier("status_bar_height","dimen","android");
if(resourceId > 0){
return getResources().getDimensionPixelSize(resourceId);
}
return 0;
}
以上所有的设置都必须在setContentView()调用之前进行设置
附上完整代码:
/**
* <pre>
* author : QB
* time : 2019/3/25
* version : v1.0.0
* qq : 952722763
* email : [email protected]
* desc :
* </pre>
*/
public class MyViewActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DensityUtils.setDensity(getApplication(),this);
//1.设置成全屏
requestWindowFeature(Window.FEATURE_NO_TITLE);
Window widow = getWindow();
widow.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
//判断手机是不是刘海屏
boolean isHasCutOut = isHasCutOut(widow);
if(isHasCutOut){
//2.让内容区域延伸至刘海区
WindowManager.LayoutParams layoutParams = widow.getAttributes();
/**
* * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT 全屏模式,内容下移,非全屏模式不受影响
* * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES 允许内容延伸至刘海
* * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER 不允许内容延伸至刘海
*/
layoutParams.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
widow.setAttributes(layoutParams);
//3.设置成沉浸式
int flag = View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
int visibility = widow.getDecorView().getSystemUiVisibility();
visibility |= flag; // 追加沉浸式设置
widow.getDecorView().setSystemUiVisibility(visibility);
}
setContentView(R.layout.activity_screenfit_density_view);
//是否需要避开刘海
RelativeLayout rl = findViewById(R.id.rel);
int height = getCutOutHeight();
rl.setPadding(rl.getPaddingLeft(),getCutOutHeight(),rl.getPaddingRight(),rl.getPaddingBottom());
/*View view = findViewById(R.id.view);
RelativeLayout.LayoutParams rl = (RelativeLayout.LayoutParams) view.getLayoutParams();
rl.topMargin = getCutOutHeight();
view.setLayoutParams(rl);*/
}
/**
* 判断是不是刘海屏
* @return
*/
private boolean isHasCutOut(Window widow){
DisplayCutout displayCutout = getDisplayCutout(widow);
if(displayCutout != null){
if(displayCutout.getBoundingRects() != null && displayCutout.getBoundingRects().size()>0 && displayCutout.getSafeInsetTop()>0){
return true;
}
}
return false;
}
private DisplayCutout getDisplayCutout(Window widow){
DisplayCutout displayCutout = null;
//9.0以后才有刘海屏
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P){
View rootView = widow.getDecorView();
WindowInsets rootWindowInsets = rootView.getRootWindowInsets();
if(rootWindowInsets != null){
displayCutout = rootWindowInsets.getDisplayCutout();
}
}
return displayCutout;
}
/**
* 通常情况下状态栏的高度可以看成是刘海的高度,刘海的高度<=状态栏高度
* @return
*/
private int getCutOutHeight(){
int resourceId = getResources().getIdentifier("status_bar_height","dimen","android");
if(resourceId > 0){
return getResources().getDimensionPixelSize(resourceId);
}
return 0;
}
}
运行效果如下: