美文网首页
屏幕适配

屏幕适配

作者: ArcherZang | 来源:发表于2020-04-03 11:00 被阅读0次

    部分内容转载
    作者:Alan_兰哥
    链接:https://www.jianshu.com/p/0d61f9dffb14

    屏幕尺寸、屏幕分辨率、屏幕像素密度

    屏幕尺寸:屏幕对角线长度,单位是英寸,我们常说的多少多少寸,比如4.7存手机、5.7存手机,指的就是这个。
    屏幕分辨率:如 1920×1080,是指在手机屏幕的像素点的个数,单位是px,1px = 1 像素点,一般是纵向像素 × 横向像素,意味着高有 1920 个像素点,宽有 1080 个像素点。
    屏幕像素密度:是指每英寸上的像素点数,单位是 dpi(dotper inch)。像素密度和屏幕尺寸和屏幕分辨率有关,它是由对角线的像素点数除以屏幕的大小得到的,关系如下


    pingmu0.png

    dp、dip、dpi、sp、px

    dp:是Android 特有的,意为密度无关像素,Google 发布的 BASELINE(基准线)为 160,以此为基准。
    dip:Density Independent Pixels,同dp一个意思,目前废弃了,一般都写dp。
    dpi:即为屏幕像素密度的单位
    sp:Scale-IndependentPixels的缩写,可以根据文字大小首选项自动进行缩放。Google推荐我们使用12sp以上的大小,通常可以使用12sp,14sp,18sp,22sp,为避免精度损失,建议最好不要使用奇数和小数。
    px:就是我们常说的像素


    pingmu1.png
    pingmu2.png

    屏幕分辨率限定符(不推荐)

    屏幕分辨率限定符适配需要在 res 文件夹下创建各种屏幕分辨率对应的 values-xxx 文件夹,如下图:


    pingmu3.png

    然后根据一个基准分辨率,例如基准分辨率为 1280x720,将宽度分成 720 份,取值为 1px~720px,将高度分成 1280 份,取值为 1px~1280px,生成各种分辨率对应的 dimens.xml 文件。如下分别为分辨率 1280x720 与 1920x1080 所对应的横向dimens.xml 文件:


    pingmu4.PNG

    假设设计图上的一个控件的宽度为 720px,那么布局中就写 android:layout_width="@dimen/x720" ,当运行程序的时候,系统会根据设备的分辨率去寻找对应的 dimens.xml 文件。例如运行在分辨率为 1280x720 的设备上,系统会自动找到对应的 values-1280x720 文件夹下的 lay_x.xml 文件,由上图可知 x720 对应的值为
    720.px,可铺满该屏幕宽度。运行在分辨率为 1920x1080 的设备上,系统会自动找到对应的 values-1920x1080 文件夹下的 lay_x.xml 文件,由上图可知 x720 对应的值为 1080.0px,可铺满该屏幕宽度。这样就达到了屏幕适配的要求!

    smallestWidth 限定符

    smallestWidth 限定符适配原理与屏幕分辨率限定符适配原理一样,系统都是根据限定符去寻找对应的 dimens.xml 文件。例如程序运行在最小宽度为 360dp 的设备上,系统会自动找到对应的 values-sw360dp 文件夹下的 dimens.xml 文件。区别就在于屏幕分辨率限定符适配是拿 px 值等比例缩放,而 smallestWidth 限定符适配是拿 dp 值来等比缩放而已。需要注意的是“最小宽度”是不区分方向的,即无论是宽度还是高度,哪一边小就认为哪一边是“最小宽度”。如下分别为最小宽度为 360dp 与最小宽度为 640dp 所对应的 dimens.xml 文件:


    pingmu5.PNG

    可以通过ScreenMatch生成

    需要自己安装ScreenMatch插件,第一次生成,会生成两个文件
    将screenMatch_example_dimens拷贝到value目录,并且重命名成dimens;
    screenMatch.properties文件中我们可以设置匹配哪些宽带的屏幕、忽略哪些以及匹配 哪个项目等等。
    然后我再做一次生成操作就可以生成了各种swXXXdp目录了
    最后使用android:layout_width="@dimen/x720"


    pingmu6.PNG
    pingmu7.PNG
    pingmu8.PNG

    平板适配

    新建layout-sw720dp目录,里面添加layout布局文件。 使用平板打开APP自动切换。

    也可以使用最小宽度符以及下面的适配 。

    常见适配方案

    布局适配

    1. 避免写死控件尺寸,使用wrap_content, match_parent
    2. LinearLayout xxx:weight="0.5“
    3. RelativeLayout xxx:layout_centerInParent="true" ...
    4. ContraintLayout xxx:layout_constraintLeft_toLeftOf="parent"...
      相对定位,可以设置偏移量0-1百分比
      角度定位
      边距margin分显隐性,就是不显示的时候也存在
      可以设置宽高比,横向纵向线可以设置百分比,可以设置占位view(不绘制),
      组成链式可以设置权重和链式样式,
      Barrier设置屏障,约束关联view一侧效果
      Group,一起控制显隐性
      Optimizer对二次测量进行优化
    5. Percent-support-lib xxx:layout_widthPercent="30%" ...宽占父布局宽度多少,
      LayoutParams中属性的获取
      onMeasure中,改变params.width为百分比计算结果,测量
      如果测量值过小且设置的w/h是wrap_content,重新测量

    图片资源适配

    1. .9图或者SVG图实现缩放
    2. 备用位图匹配不同分辨率

    动态运行时加载

    (关于小分辨率手机看到比大分辨率手机大,跟像素点大小有关,可以参照权重处理,
    其实权重比也有问题,就是屏幕分辩率带来的问题,不同屏幕其分辨率比值是不一样的,导致大分辨率上是长方形 ,小分辨率上是正方形)

    自定义 分辨率布局

    设计稿有标准尺寸1920X1080(设计稿针对的屏幕尺寸)
    那View控件宽高(设计稿view的宽高)*当前手机屏幕和标准尺寸(设计稿标准尺寸)的缩放比,得到view控件的真实值。
    另外 margin也有计算。
    最外层加一个自己的布局,在测量时完成上面的逻辑

       @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            if (!flag) { //防止两次测量
                flag = true;
                //获取横竖方向等比
                float scaleX = Utils.getInstance(getContext()).getHorizontalScale();
                float scaleY = Utils.getInstance(getContext()).getVerticalScale();
                //子View个数
                int childCount = getChildCount();
                for (int i = 0; i < childCount; i++) {
                    View child = getChildAt(i);
                    LayoutParams params = (LayoutParams) child.getLayoutParams();
                    params.width = (int) (params.width * scaleX);
                    params.height = (int) (params.height * scaleY);
                    params.leftMargin = (int) (params.leftMargin * scaleX);
                    params.rightMargin = (int) (params.rightMargin * scaleX);
                    params.topMargin = (int) (params.topMargin * scaleY);
                    params.bottomMargin = (int) (params.bottomMargin * scaleY);
                    child.setPadding((int) (child.getPaddingLeft() * scaleX), (int) (child.getPaddingTop() * scaleY),
                            (int) (child.getPaddingRight() * scaleX), (int) (child.getPaddingBottom() * scaleY));
                }
            }
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    
    public class Utils {
        private static Utils utils;
        private Context mContext;
        //这里是设计稿参考宽高
        private static final float STANDARD_WIDTH = 1080;
        private static final float STANDARD_HEIGHT = 1920;
        //这里是屏幕显示宽高
        private int mDisplayWidth;
        private int mDisplayHeight;
    
        public Utils(Context context) {
            mContext = context;
            //获取屏幕宽高
            if (mDisplayWidth == 0 || mDisplayHeight == 0) {
                WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
                if (windowManager != null) {
                    //宽高获取
                    DisplayMetrics displayMetrics = new DisplayMetrics();
                    //如果不是NavigationBar沉浸式(不包含NavigationBar,如果屏幕有NavigationBar会比real小即真实分辨率小)
                    windowManager.getDefaultDisplay().getMetrics(displayMetrics);
    //                windowManager.getDefaultDisplay().getRealMetrics(displayMetrics);//真实屏幕宽高
                    //判断当前的横竖屏
                    if (displayMetrics.widthPixels > displayMetrics.heightPixels) {
                        //横屏
                        mDisplayWidth = displayMetrics.heightPixels;
                        mDisplayHeight = displayMetrics.widthPixels - getStatusBarHeight(context);
                    } else {
                        //竖屏
                        mDisplayWidth = displayMetrics.widthPixels;
                        mDisplayHeight = displayMetrics.heightPixels - getStatusBarHeight(context);
                    }
                }
            }
        }
    
        public static Utils getInstance(Context context) {
            if (utils == null) {
                utils = new Utils(context);
            }
            return utils;
        }
    
        //获取状态栏高度
        public int getStatusBarHeight(Context context) {
            int resId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
            if (resId > 0) {
                return context.getResources().getDimensionPixelSize(resId);//获取具体的像素值
            }
            return 0;
        }
    
        //获取水平方向的缩放比例
        public float getHorizontalScale() {
            return mDisplayWidth / STANDARD_WIDTH;
        }
    
        //获取垂直方向的缩放比例
        public float getVerticalScale() {
            return mDisplayHeight / (STANDARD_HEIGHT - getStatusBarHeight(mContext));
        }
    }
    
    
    自定义 百分比布局

    和谷歌提供的百分比布局类似

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            //获取父容器宽高
            if (!flag) {
                flag = true;
                int widthSize = MeasureSpec.getSize(widthMeasureSpec);
                int heightSize = MeasureSpec.getSize(heightMeasureSpec);
                //给子控件设置修改后的属性值
                int childCount = getChildCount();
                for (int i = 0; i < childCount; i++) {
                    //获取子控件
                    View child = getChildAt(i);
                    //获取子控件LayoutParams
                    ViewGroup.LayoutParams layoutParams = child.getLayoutParams();
                    //判断子控件是否是百分比布局属性
                    if (checkLayoutParams(layoutParams)) {
                        //是
                        LayoutParams lp = (LayoutParams) layoutParams;
                        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) {
                            lp.width = (int) (widthSize * widthPercent);
                        }
                        if (heightPercent > 0) {
                            lp.height = (int) (heightSize * heightPercent);
                        }
                        if (marginLeftPercent > 0) {
                            lp.leftMargin = (int) (widthSize * marginLeftPercent);
                        }
                        if (marginRightPercent > 0) {
                            lp.rightMargin = (int) (widthSize * marginRightPercent);
                        }
                        if (marginTopPercent > 0) {
                            lp.topMargin = (int) (heightSize * marginTopPercent);
                        }
                        if (marginBottomPercent > 0) {
                            lp.bottomMargin = (int) (heightSize * marginBottomPercent);
                        }
                    }
                }
            }
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    
        @Override
        protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
            return p instanceof LayoutParams;
        }
    
        public LayoutParams generateLayoutParams(AttributeSet attrs) {
            return new LayoutParams(getContext(), attrs);
        }
    
        /**
         * 1、创建自定义属性
         * 2、在容器中去创建一个静态内部类LayoutParams
         * 3、在LayoutParams构造方法中获取自定义属性
         * 4、onMeasure中给子控件设置修改后的属性值
         */
    
        public static class LayoutParams extends RelativeLayout.LayoutParams {
            private float widthPercent;
            private float heightPercent;
            private float marginLeftPercent;
            private float marginRightPercent;
            private float marginTopPercent;
            private float marginBottomPercent;
    
            public LayoutParams(Context c, AttributeSet attrs) {
                super(c, attrs);
                //3、在LayoutParams构造方法中获取自定义属性 解析自定义属性
                TypedArray typedArray = c.obtainStyledAttributes(attrs, R.styleable.PercentLayout);
                widthPercent = typedArray.getFraction(R.styleable.PercentLayout_widthPercent, 1, 2, 0);
                heightPercent = typedArray.getFraction(R.styleable.PercentLayout_heightPercent, 1, 2, 0);
                marginLeftPercent = typedArray.getFraction(R.styleable.PercentLayout_marginLeftPercent, 1, 2, 0);
                marginRightPercent = typedArray.getFraction(R.styleable.PercentLayout_marginRightPercent, 1, 2, 0);
                marginTopPercent = typedArray.getFraction(R.styleable.PercentLayout_marginTopPercent, 1, 2, 0);
                marginBottomPercent = typedArray.getFraction(R.styleable.PercentLayout_marginBottomPercent, 1, 2, 0);
                typedArray.recycle();//回收
            }
        }
    

    attrs

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <declare-styleable name="PercentLayout">
            <attr name="widthPercent" format="fraction" />
            <attr name="heightPercent" format="fraction" />
            <attr name="marginLeftPercent" format="fraction" />
            <attr name="marginRightPercent" format="fraction" />
            <attr name="marginTopPercent" format="fraction" />
            <attr name="marginBottomPercent" format="fraction" />
        </declare-styleable>
    </resources>
    
    <?xml version="1.0" encoding="utf-8"?>
    <com.dn_alan.myapplication.percent.PercentLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".MainActivity">
    
        <TextView
            android:id="@+id/textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#f00"
            app:heightPercent="5%"
            app:widthPercent="90%"
            app:marginLeftPercent="30%"
            app:marginRightPercent="30%"
            app:marginTopPercent="1%"
            tools:ignore="MissingPrefix" />
    
        <TextView
            android:id="@+id/textView2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/textView"
            android:background="#f00"
            app:heightPercent="30%"
            app:marginBottomPercent="30%"
            app:marginLeftPercent="30%"
            app:marginRightPercent="30%"
            app:marginTopPercent="30%"
            app:widthPercent="30%"
            tools:ignore="MissingPrefix" />
    
    
    </com.dn_alan.myapplication.percent.PercentLayout>
    
    像素密度布局(像素密度包含长宽,所以和之前分辨率布局类似但是也有不一样的地方)

    重点就是改变displayMetrics
    dm.density = targetDensity; //(dpi/160) 后得到的值
    dm.scaledDensity = targetScaleDensity;//字体缩放
    dm.densityDpi = targetDensityDpi; //dpi
    并注册组件监听,判断字体是否更改缩放,生成适配当前的缩放;

    也可以 在application中注册activity声明周期回调

    public class DensityUtils {
        private static final float WIDTH = 360;//参考宽度(dp)
        private static float appDensity;//表示屏幕密度
        private static float appScaleDensity;//字体缩放比例,默认为appDensity
    
        public static void setDensity(final Application application, Activity activity) {
            //获取当前屏幕信息
            DisplayMetrics displayMetrics = application.getResources().getDisplayMetrics();
            if (appDensity == 0) {
                //初始化赋值
                appDensity = displayMetrics.density;
                appScaleDensity = displayMetrics.scaledDensity;
                //监听字体变化
                application.registerComponentCallbacks(new ComponentCallbacks() {
                    @Override
                    public void onConfigurationChanged(Configuration newConfig) {
                        //字体发生更改,重新计算scaleDensity
                        if (newConfig != null && newConfig.fontScale > 0) {
                            appScaleDensity = application.getResources().getDisplayMetrics().scaledDensity;
                        }
                    }
    
                    @Override
                    public void onLowMemory() {
    
                    }
                });
            }
            //计算目标density scaledDensity
            float targetDensity = displayMetrics.widthPixels / WIDTH;//1080/360=3;
            float targetScaleDensity = targetDensity * (appScaleDensity / appDensity);
            int targetDensityDpi = (int) (targetDensity * 160);
            //替换Activity的值
            //px = dp * (dpi / 160)
            DisplayMetrics dm = activity.getResources().getDisplayMetrics();
            dm.density = targetDensity;  //(dpi/160) 后得到的值
            dm.scaledDensity = targetScaleDensity;
            dm.densityDpi = targetDensityDpi;  //dpi
        }
    }
    
    @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    //        //设置density
            DensityUtils.setDensity(getApplication(), this);
            setContentView(R.layout.activity_density);
        }
    
    今日头条

    也是改的这几个值

    做了架构设计同时提供页面单独适配包括fragment和取消适配,

    ---->AutoSizeConfig.init ()
    给了初始值放在获取MetaData过慢
    mDesignWidthInDp = 1080;
    mDesignHeightInDp = 1920;
    getMetaData(application);
    
    mActivityLifecycleCallbacks = new ActivityLifecycleCallbacksImpl(new WrapperAutoAdaptStrategy(strategy == null ? new DefaultAutoAdaptStrategy() : strategy));
    application.registerActivityLifecycleCallbacks(mActivityLifecycleCallbacks);
    
    ----->ActivityLifecycleCallbacksImpl.onActivityCreated()
        1. 适配androidx或者v4注册fargment声明周期
                        ((androidx.fragment.app.FragmentActivity) activity).getSupportFragmentManager().registerFragmentLifecycleCallbacks(mFragmentLifecycleCallbacksToAndroidx, true);
            最终也是调用的applyAdapt方法只不过第一个参数是fagement自己
        2. 调用设置方法
            //Activity 中的 setContentView(View) 一定要在 super.onCreate(Bundle); 之后执行
            if (mAutoAdaptStrategy != null) {
                mAutoAdaptStrategy.applyAdapt(activity, activity);
            }
    
    ----->DefaultAutoAdaptStrategy.applyAdapt(Object target, Activity activity)两个参数都传的当前acitivity
        如果你有自定义就是走自定义没有走默认类DefaultAutoAdaptStrategy()
    1. 检查是否开启了外部三方库的适配模式, 只要不主动调用 ExternalAdaptManager 的方法, 下面的代码就不会执行。里面还会判断是否实现取消接口,然后才走第三方。
    2. target是不是实现了CancelAdapt,即取消,走AutoSize.cancelAdapt(activity),把值改回原来的还会走下面的方法
    3. 如果 target 实现 CustomAdapt 接口表示该 target 想自定义一些用于适配的参数, 从而改变最终的适配效果AutoSize.autoConvertDensityOfCustomAdapt(activity, (CustomAdapt) target);--->autoConvertDensity(activity, sizeInDp, customAdapt.isBaseOnWidth())
    4. 最终走AutoSize.autoConvertDensityOfGlobal(activity)
        
    ------>AutoSize.autoConvertDensityOfGlobal(activity)
        基于高度还是宽度走不同方法
        autoConvertDensityBaseOnWidth(activity, AutoSizeConfig.getInstance().getDesignWidthInDp());
        autoConvertDensityBaseOnHeight(activity, AutoSizeConfig.getInstance().getDesignHeightInDp());
        两者最终调用了autoConvertDensity(activity, designWidthInDp, true);第三个参数宽是true高是false
    ------>AutoSize.autoConvertDensity(Activity activity, float sizeInDp, boolean isBaseOnWidth)
            根据传入对比值+设计大小+屏幕大小+字体放大比例以及MODE_MASK算出key
            int key = Math.round((sizeInDp + subunitsDesignSize + screenSize) * AutoSizeConfig.getInstance().getInitScaledDensity()) & ~MODE_MASK;
            isBaseOnWidth是true用屏幕宽比值算,否则用屏幕高比值算
            targetDensity = AutoSizeConfig.getInstance().getScreenWidth() * 1.0f / sizeInDp;
            根据targetDensity计算targetDensityDpi,targetScaledDensity,targetXdpi,targetScreenWidthDp,targetScreenHeightDp;
            放入缓存方便下次取出
            mCache.put(key, new DisplayMetricsInfo(targetDensity, targetDensityDpi, targetScaledDensity, targetXdpi, targetScreenWidthDp, targetScreenHeightDp));
            setDensity(activity, targetDensity, targetDensityDpi, targetScaledDensity, targetXdpi);
            setScreenSizeDp(activity, targetScreenWidthDp, targetScreenHeightDp);
    ------>1. AutoSize.setDensity()
        兼容Miui获取Resources.class.getDeclaredField("mTmpMetrics"),一个是activity.getResources(),一个是getApplication().getResources();两者顺序是线activity再app
         setDensity(DisplayMetrics displayMetrics, float density, int densityDpi, float scaledDensity, float xdpi)
            设置三个值density,densityDpi,scaledDensity
            if (AutoSizeConfig.getInstance().getUnitsManager().isSupportDP()) {
                displayMetrics.density = density;
                displayMetrics.densityDpi = densityDpi;
            }
            if (AutoSizeConfig.getInstance().getUnitsManager().isSupportSP()) {
                displayMetrics.scaledDensity = scaledDensity;
            }
            如果有附属单位转换displayMetrics.xdpi的值
    ------->2. AutoSize.setScreenSizeDp(Configuration configuration, int screenWidthDp, int screenHeightDp)
                得到activityConfiguration和appConfiguration修改screenWidthDp和screenHeightDp
                Configuration activityConfiguration = activity.getResources().getConfiguration();
                Configuration appConfiguration = AutoSizeConfig.getInstance().getApplication().getResources().getConfiguration()
    

    刘海屏适配

    系统级适配规则

    Google默认的适配刘海屏策略是这样的

    1. 如果应用未适配刘海屏,需要系统对全屏显示的应用界面做特殊移动处理(竖屏下移处理,横屏右移处),因此此处出现了黑边的现象;如果应用页面布局不能做到自适应,就会出现布局问题;如果应用布局能够做到自适应,也会有黑边无法全屏显示的体验问题
      2)如果有状态栏的App,则不受刘海屏的影响(有状态肯定不是全屏,那么就不会有下移的风险)
      在Android P版本中,通过DisplayCutout 类,可以确定非功能区域(刘海屏)的位置和形状
      LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT:
      只有当DisplayCutout完全包含在系统状态栏中时,才允许窗口延伸到DisplayCutout区域显示。
      LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER:
      该窗口决不允许与DisplayCutout区域重叠。
      LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES :
      该窗口始终允许延伸到屏幕短边上的DisplayCutout

    第一种样式::LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
        WindowManager.LayoutParams lp = this.getWindow().getAttributes();
        lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
        this.getWindow().setAttributes(lp);
        setContentView(R.layout.activity_android_p_demo_layout);
    }
    

    从图中可以看出,这个并不是完美的适配方案


    pingmu11.png

    第二种样式样式:LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                               WindowManager.LayoutParams.FLAG_FULLSCREEN);
        WindowManager.LayoutParams lp = this.getWindow().getAttributes();
        lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
        this.getWindow().setAttributes(lp);
        setContentView(R.layout.activity_android_p_demo_layout);
    }
    
    pingmu12.png

    可以看出显示效果和DEFAULT是一致的,LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER是不允许使用刘海屏区域,而LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT全屏窗口不允许使用刘海屏区域

    第三种样式样式:LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
        WindowManager.LayoutParams.FLAG_FULLSCREEN);
        WindowManager.LayoutParams lp = this.getWindow().getAttributes();
        lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
        this.getWindow().setAttributes(lp);
        setContentView(R.layout.activity_android_p_demo_layout);
    }
    

    可以看出LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES已经允许全屏app使用刘海屏了,只不过状态栏那边是白色


    pingmu13.png
    //设置页面延伸到刘海区显示
            WindowManager.LayoutParams lp = mAc.getWindow().getAttributes();
            lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
            getWindow().setAttributes(lp);
     //使内容出现在status bar后边,如果要使用全屏的话再加上View.SYSTEM_UI_FLAG_FULLSCREEN
            requestWindowFeature(Window.FEATURE_NO_TITLE);
            View decorView = mAc.getWindow().getDecorView();
            int systemUiVisibility = decorView.getSystemUiVisibility();
            int flags = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                    | View.SYSTEM_UI_FLAG_FULLSCREEN;
            systemUiVisibility |= flags;
            mAc.getWindow().getDecorView().setSystemUiVisibility(systemUiVisibility);
    

    WindowInsets.getDisplayCutout() 来获得 DisplayCutout object,里面包含了几个有用的方法:
    getBoundingRects():获取刘海 / Cutout 所在的矩形区域的位置,多个刘海则返回多个区域(单位:像素)。
    getSafeInsetLeft() / getSafeInsetTop() / getSafeInsetRight() / getSafeInsetBottom() :返回安全区上下左右的偏移值(单位:像素)

    套用小米的处理话来说

    处理好同一页面,进入与退出全屏模式(fullscreen mode)的过渡
    因为在默认模式 / LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT 下,系统针对全屏与非全屏的页面,耳朵区的显示逻辑不一样。如果开发者没有处理好,容易出现页面可用区域跳变的问题。针对这种页面,我们建议开发者主动声明是否使用耳朵区,以避免跳变。

    我们总结一下:
     if (context instanceof AppCompatActivity) {
                AppCompatActivity app = (AppCompatActivity) context;
                if (app.getSupportActionBar() != null) {
                    app.getSupportActionBar().hide();
                }
                //LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
                // 只有当DisplayCutout完全包含在系统状态栏中时,才允许窗口延伸到DisplayCutout区域显示。
                //LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
                //该窗口决不允许与DisplayCutout区域重叠,但是会把状态栏变成黑色,效果很差,建议这种情况使用DEFAULT。
                //LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
                //该窗口始终允许延伸到屏幕短边上的DisplayCutout区域。
                //PS:如果需要应用的布局延伸到刘海区显示,需要设置SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN。
                if (isUseImmersiveBars) {//是否使用沉浸式状态栏
                    app.getWindow().getDecorView().
                        setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); //设置页面全屏显示
                } else {
                    if (isUseCutout) {//是否使用刘海
                        app.getWindow().getDecorView().
                            setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN |
                                                  View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
                        //设置页面全屏显示
                    } else {
                        app.getWindow().getDecorView().setSystemUiVisibility(0);
                    }
                }
                if (cutoutMode == -1) {//是否做刘海屏适配
                    cutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
                }
                WindowManager.LayoutParams lp = app.getWindow().getAttributes();
                lp.layoutInDisplayCutoutMode = cutoutMode;
                //设置页面延伸到刘海区显示
                app.getWindow().setAttributes(lp);
            }
    
    小米特殊处理:声明 Maximum Aspect Ratio

    Android 标准接口中,支持应用声明其支持的最大屏幕高宽比(maximum aspect ratio)。具体声明如下,其中的 ratio_float 被定义为是高除以宽,以 16:9 为例,ratio_float = 16/9 = 1.778 (18:9则为2.0)。

    <application>
        <meta-data android:name="android.max_aspect" android:value="ratio_float" />
    </application>
    

    若开发者没有声明该属性,ratio_float 的默认值为1.86,小于2.0,因此这类应用在全面屏手机上,默认不会全屏显示,屏幕底部会留黑。考虑到将有更多 19.5:9 甚至更长的手机出现,建议开发者声明 Maximum Aspect Ratio ≥ 2.2 或更多。值得一提的是,如果应用的 android:resizeableActivity 已经设置为 true,就不必设置 Maximum Aspect Ratio 了

    Android Q
    /**
         * 小米删除刘海区域
         *
         * @param context context
         */
        public static void clearExtraFlag(Context context) {
            int flag = 0x00000100 | 0x00000200 | 0x00000400;
            //0x00000100 开启配置
            //0x00000200 竖屏配置
            //0x00000400 横屏配置
            //<meta-data
            // android:name="notch.config"
            // android:value="portrait|landscape"/>
            if (context instanceof AppCompatActivity) {
                AppCompatActivity app = (AppCompatActivity) context;
                try {
                    Method method = Window.class.getMethod("clearExtraFlags",
                            int.class);
                    method.invoke(app.getWindow(), flag);
                } catch (Exception e) {
                    Log.i(TAG, "addExtraFlags not found.");
                }
            }
        }
    
        /**
         * 小米添加刘海区域
         *
         * @param context context
         */
        public static void addExtraFlag(Context context) {
            int flag = 0x00000100 | 0x00000200 | 0x00000400;
            if (context instanceof AppCompatActivity) {
                AppCompatActivity app = (AppCompatActivity) context;
                try {
                    Method method = Window.class.getMethod("addExtraFlags",
                            int.class);
                    method.invoke(app.getWindow(), flag);
                } catch (Exception e) {
                    Log.i(TAG, "addExtraFlags not found.");
                }
            }
        }
    
         /*刘海屏全屏显示FLAG*/
        private static final int FLAG_NOTCH_SUPPORT = 0x00010000;
        /**
         * 设置应用窗口在华为刘海屏手机使用刘海区
         *
         * @param window 应用页面window对象
         */
        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(TAG, "other Exception");
            }
        }
    
        /**
         * 设置应用窗口在华为刘海屏手机使不用刘海区
         *
         * @param window 应用页面window对象
         */
        public static void setNotFullScreenWindowLayoutInDisplayCutout(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("clearHwFlags", int.class);
                method.invoke(layoutParamsExObj, FLAG_NOTCH_SUPPORT);
            } catch (Exception e) {
                Log.e(TAG, "other Exception");
            }
        }
    
    刘海屏判断
    ANDROID P
    if (context instanceof AppCompatActivity) {
        AppCompatActivity app = (AppCompatActivity) context;
        DisplayCutout cutout = app.getWindow().getDecorView().getRootWindowInsets().getDisplayCutout();
        if (cutout == null) {
            // listener可以无视
            // 这里理解为无刘海
            if (listener != null) {
                listener.isHasCutout(false);
            }
            if (BuildConfig.DEBUG) {
                Log.e(TAG, "cutout==null, is not notch screen");//通过cutout是否为null判断是否刘海屏手机
            }
        } else {
            List<Rect> rects = cutout.getBoundingRects();
            if (rects == null || rects.size() == 0) {
                // listener可以无视
                // 这里理解为无刘海
                listener.isHasCutout(false);
            } else {
                listener.isHasCutout(true);
                // listener可以无视
                // 这里理解为有刘海
                //如需用到cutout信息,则使用
                if (listener instanceof OnCutoutDetailListener) {
                    ((OnCutoutDetailListener) listener).onCutout(cutout);
                }
            }
        }
    }
    
    Android Q

    各家方法判断

        
        /**
         * 判断用户是否开启了隐藏刘海区域
         *
         * @param context
         * @return
         */
        public static boolean isHideNotchScreen4Xiaomi(Context context) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                return Settings.Global.getInt(context.getContentResolver(), "force_black", 0) == 1;
            }else{
                return false;
            }
        }
    
        /**
         * 判断小米是否有刘海屏
         *
         * @return 是否有刘海屏
         */
        public static boolean hasNotchInScreenAtXiaomi() {
            return SystemProperties.getint("ro.miui.notch", 0) == 1;
        }
    
        /**
         * 华为手机是否有刘海屏
         *
         * @param context context
         * @return 是否有刘海屏
         */
        public static boolean hasNotchInScreenAtHuawei(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(TAG, "hasNotchInScreen ClassNotFoundException");
            } catch (NoSuchMethodException e) {
                Log.e(TAG, "hasNotchInScreen NoSuchMethodException");
            } catch (Exception e) {
                Log.e(TAG, "hasNotchInScreen Exception");
            }
            return ret;
        }  
    
     /**
         * 判断是否有刘海屏
         *
         * @param context context
         * @return 是否有刘海屏
         */
        private static final int NOTCH_IN_SCREEN_VOIO = 0x00000020;//是否有凹槽
    
        public static boolean hasNotchInScreenAtVoio(Context context) {
            boolean ret = false;
            try {
                ClassLoader cl = context.getClassLoader();
                Class FtFeature = cl.loadClass("android.util.FtFeature");
                Method get = FtFeature.getMethod("isFeatureSupport", int.class);
                ret = (boolean) get.invoke(FtFeature, NOTCH_IN_SCREEN_VOIO);
    
            } catch (ClassNotFoundException e) {
                Log.e(TAG, "hasNotchInScreen ClassNotFoundException");
            } catch (NoSuchMethodException e) {
                Log.e(TAG, "hasNotchInScreen NoSuchMethodException");
            } catch (Exception e) {
                Log.e(TAG, "hasNotchInScreen Exception");
            } finally {
                return ret;
            }
        }
    
     /**
         * 判断oppo是否有刘海屏
         *
         * @param context context
         * @return 是否有刘海屏
         */
        public static boolean hasNotchInScreenAtOppo(Context context) {
            return context.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism");
        }
    

    获取刘海高度

        /**
         * 获得小米刘海屏幕高度
         *
         * @param context context
         * @return 小米屏幕高度
         */
        public static int getNotchXiaomiHeight(Context context) {
            int result = 0;
            int resourceId = context.getResources().getIdentifier("notch_height", "dimen", "android");
            if (resourceId > 0) {
                result = context.getResources().getDimensionPixelSize(resourceId);
            }
            return result;
        }
        /**
         * 获得小米刘海屏幕宽度
         *
         * @param context context
         * @return 小米屏幕宽度
         */
        public static int getNotchXiaomiWidth(Context context) {
            int result = 0;
            int resourceId = context.getResources().getIdentifier("notch_width", "dimen", "android");
            if (resourceId > 0) {
                result = context.getResources().getDimensionPixelSize(resourceId);
            }
            return result;
        }
    
     /**
         * 获取华为刘海屏的刘海尺寸
         *
         * @param context context
         * @return 刘海尺寸
         */
        public static int[] getNotchSize4Huawei(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(TAG, "getNotchSize ClassNotFoundException");
            } catch (NoSuchMethodException e) {
                Log.e(TAG, "getNotchSize NoSuchMethodException");
            } catch (Exception e) {
                Log.e(TAG, "getNotchSize Exception");
            }
            return ret;
        }
    
    状态栏(沉浸式两者:一种设置状态栏颜色或者透明,一种隐藏状态栏,因为全屏后刘海位置出现黑色所以才会刘海屏适配)

    状态栏高度比刘海高 。所以如果需要移动,就移动状态栏高度。
    关于状态栏介绍:https://www.jianshu.com/p/752f4551e134

    /**
     * 获取状态栏高度
     *
     * @param activity
     * @return
     */
    public static int getStatusBarHeight(Activity activity) {
        int statusBarHeight = 0;
        int resourceId = activity.getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resourceId > 0) {
            statusBarHeight = activity.getResources().getDimensionPixelSize(resourceId);
        }
        return statusBarHeight;
    }
    
    /**
     * 修改状态栏为全透明
     *
     * @param activity
     */
    @TargetApi(19)
    public static void transparencyBar(Activity activity) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            Window window = activity.getWindow();
            window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
            window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
            window.setStatusBarColor(Color.TRANSPARENT);
    
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            Window window = activity.getWindow();
            window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
                    WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        }
    }
    
     /**
         * 回复状态栏
         * PS:如果一开始有沉浸式状态栏,然后再恢复初始化,那么状态栏会变成黑色背景
         * 如用到此方法,可以做延迟操作改变自己想要的状态栏颜色。
         *
         * @param activity
         */
        @TargetApi(19)
        public static void restoreBar(Activity activity, int color) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                Window window = activity.getWindow();
                window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
                window.getDecorView().setSystemUiVisibility(0);
                window.clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                Window window = activity.getWindow();
                window.setFlags(0,
                        WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            }
            setStatusBarColor(activity, color);
        }
    
    /**
         * 修改状态栏颜色,支持4.4以上版本
         *
         * @param activity
         * @param colorId
         */
        public static void setStatusBarColor(Activity activity, int colorId) {
    
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                Window window = activity.getWindow();
    //      window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
                window.setStatusBarColor(activity.getResources().getColor(colorId));
            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                //使用SystemBarTint库使4.4版本状态栏变色,需要先将状态栏设置为透明
                transparencyBar(activity);
                SystemBarTintManager tintManager = new SystemBarTintManager(activity);
                tintManager.setStatusBarTintEnabled(true);
                tintManager.setStatusBarTintResource(colorId);
            }
        }
    
        public static void setStatusBarColor(Activity activity, int colorId) {
    
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                Window window = activity.getWindow();
    //      window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
                window.setStatusBarColor(activity.getResources().getColor(colorId));
            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                //使用SystemBarTint库使4.4版本状态栏变色,需要先将状态栏设置为透明
                transparencyBar(activity);
                SystemBarTintManager tintManager = new SystemBarTintManager(activity);
                tintManager.setStatusBarTintEnabled(true);
                tintManager.setStatusBarTintResource(colorId);
            }
        }
    
    状态栏字色和图标浅黑色
    /**
     * 状态栏亮色模式,设置状态栏黑色文字、图标,
     * 适配4.4以上版本MIUIV、Flyme和6.0以上版本其他Android
     *
     * @param activity
     * @return 1:MIUUI 2:Flyme 3:android6.0
     */
    public static int statusBarLightMode(Activity activity) {
        return statusBarLightMode(activity.getWindow());
    }
    
    /**
     * 状态栏亮色模式,设置状态栏黑色文字、图标,
     * 适配4.4以上版本MIUIV、Flyme和6.0以上版本其他Android
     *
     * @return 1:MIUUI 2:Flyme 3:android6.0
     */
    public static int statusBarLightMode(Window window) {
        int result = 0;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            if (MIUISetStatusBarLightMode(window, true)) {
                result = 1;
            } else if (FlymeSetStatusBarLightMode(window, true)) {
                result = 2;
            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
                result = 3;
            }
        }
        return result;
    }
    
     /**
         * 状态栏亮色模式,设置状态栏黑色文字、图标,
         * 适配4.4以上版本MIUIV、Flyme和6.0以上版本其他Android
         *
         * @param activity
         * @return 1:MIUUI 2:Flyme 3:android6.0
         */
        public static int statusBarDarkMode(Activity activity) {
            int result = 0;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                if (MIUISetStatusBarLightMode(activity.getWindow(), false)) {
                    result = 1;
                } else if (FlymeSetStatusBarLightMode(activity.getWindow(), false)) {
                    result = 2;
                } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    activity.getWindow().getDecorView().
                        setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
                    result = 3;
                }
            }
            return result;
        }
     /**
         * 设置状态栏图标为深色和魅族特定的文字风格
         * 可以用来判断是否为Flyme用户
         *
         * @param window 需要设置的窗口
         * @param dark   是否把状态栏文字及图标颜色设置为深色
         * @return boolean 成功执行返回true
         */
        public static boolean FlymeSetStatusBarLightMode(Window window, boolean dark) {
            boolean result = false;
            if (window != null) {
                try {
                    WindowManager.LayoutParams lp = window.getAttributes();
                    Field darkFlag = WindowManager.LayoutParams.class
                            .getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON");
                    Field meizuFlags = WindowManager.LayoutParams.class
                            .getDeclaredField("meizuFlags");
                    darkFlag.setAccessible(true);
                    meizuFlags.setAccessible(true);
                    int bit = darkFlag.getInt(null);
                    int value = meizuFlags.getInt(lp);
                    if (dark) {
                        value |= bit;
                    } else {
                        value &= ~bit;
                    }
                    meizuFlags.setInt(lp, value);
                    window.setAttributes(lp);
                    result = true;
                } catch (Exception e) {
    
                }
            }
            return result;
        }
    
        /**
         * 需要MIUIV6以上
         *
         * @param dark 是否把状态栏文字及图标颜色设置为深色
         * @return boolean 成功执行返回true
         */
        public static boolean MIUISetStatusBarLightMode(Window window, boolean dark) {
            boolean result = false;
            if (window != null) {
                Class clazz = window.getClass();
                try {
                    int darkModeFlag = 0;
                    Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams");
                    Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE");
                    darkModeFlag = field.getInt(layoutParams);
                    Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class);
                    if (dark) {
                        extraFlagField.invoke(window, darkModeFlag, darkModeFlag);//状态栏透明且黑色字体
                    } else {
                        extraFlagField.invoke(window, 0, darkModeFlag);//清除黑色字体
                    }
                    result = true;
    
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                        //开发版 7.7.13 及以后版本采用了系统API,旧方法无效但不会报错,所以两个方式都要加上
                        //SYSTEM_UI_FLAG_VISIBLE这个表示,不占据全屏,把状态栏会空出来,
                        if (dark) {
                            window.getDecorView().
                                setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
                                                      View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
                        } else {
                            window.getDecorView().
                                setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
                        }
                    }
                } catch (Exception e) {
    
                }
            }
            return result;
        }
    

    华为: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

    系统OS判断

    参考https://www.jianshu.com/p/ba9347a5a05a

    public class OSUtil {
    
        private static final String TAG = "Rom";
    
        public static final String ROM_MIUI = "MIUI";
        public static final String ROM_EMUI = "EMUI";
        public static final String ROM_FLYME = "FLYME";
        public static final String ROM_OPPO = "OPPO";
        public static final String ROM_SMARTISAN = "SMARTISAN";
        public static final String ROM_VIVO = "VIVO";
        public static final String ROM_QIKU = "QIKU";
    
        private static final String KEY_VERSION_MIUI = "ro.miui.ui.version.name";
        private static final String KEY_VERSION_EMUI = "ro.build.version.emui";
        private static final String KEY_VERSION_OPPO = "ro.build.version.opporom";
        private static final String KEY_VERSION_SMARTISAN = "ro.smartisan.version";
        private static final String KEY_VERSION_VIVO = "ro.vivo.os.version";
    
        private static String sName;
        private static String sVersion;
    
        public static boolean isEmui() {
            return check(ROM_EMUI);
        }
    
        public static boolean isMiui() {
            return check(ROM_MIUI);
        }
    
        public static boolean isVivo() {
            return check(ROM_VIVO);
        }
    
        public static boolean isOppo() {
            return check(ROM_OPPO);
        }
    
        public static boolean isFlyme() {
            return check(ROM_FLYME);
        }
    
        public static boolean is360() {
            return check(ROM_QIKU) || check("360");
        }
    
        public static boolean isSmartisan() {
            return check(ROM_SMARTISAN);
        }
    
        public static String getName() {
            if (sName == null) {
                check("");
            }
            return sName;
        }
    
        public static String getVersion() {
            if (sVersion == null) {
                check("");
            }
            return sVersion;
        }
    
        public static boolean check(String rom) {
            if (sName != null) {
                return sName.equals(rom);
            }
    
            if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_MIUI))) {
                sName = ROM_MIUI;
            } else if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_EMUI))) {
                sName = ROM_EMUI;
            } else if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_OPPO))) {
                sName = ROM_OPPO;
            } else if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_VIVO))) {
                sName = ROM_VIVO;
            } else if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_SMARTISAN))) {
                sName = ROM_SMARTISAN;
            } else {
                sVersion = Build.DISPLAY;
                if (sVersion.toUpperCase().contains(ROM_FLYME)) {
                    sName = ROM_FLYME;
                } else {
                    sVersion = Build.UNKNOWN;
                    sName = Build.MANUFACTURER.toUpperCase();
                }
            }
            return sName.equals(rom);
        }
    
        public static String getProp(String name) {
            String line = null;
            BufferedReader input = null;
            try {
                Process p = Runtime.getRuntime().exec("getprop " + name);
                input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024);
                line = input.readLine();
                input.close();
            } catch (IOException ex) {
                Log.e(TAG, "Unable to read prop " + name, ex);
                return null;
            } finally {
                if (input != null) {
                    try {
                        input.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            return line;
        }
    }
    

    相关文章

      网友评论

          本文标题:屏幕适配

          本文链接:https://www.haomeiwen.com/subject/oracphtx.html