什么?都0202年了,你还不会屏幕适配?

目录

写在前面

一、自定义像素适配

二、百分比布局适配

三、修改系统density适配

四、刘海屏适配

4.1、谷歌官方适配策略

4.2、国内定制ROM适配策略


写在前面

Android的屏幕适配相信大家都做过,可以说是各有各的招,对于屏幕适配真的是逢考必问,反正我每次面试都被问到了,所以它的重要性也是不言而喻了!

先来思考几个问题吧:为什么需要做屏幕适配?我们做屏幕适配的目的是什么?做了那么久的Android了,你知道都有哪些常见的屏幕适配的方式吗?OK,下面来一一解答:

  • 原因:Android设备碎片化,导致app的界面元素在不同屏幕尺寸上显示不一致
  • 目的:让布局,布局组件,资源,用户界面流程,匹配不同屏幕尺寸
  • 屏幕适配常见方式:

      1、布局适配

      

      2、图片资源适配

     

      3、用户流程匹配

      

      4、限定符适配

      

      5、刘海屏适配

      

什么❓就这❓嗯,到这里........................到这里当然不能结束啦,Because we're just getting started!咱们才刚刚开始。

上面我们给出了几个问题的答案,我们可以对照着去思考一下自己是否都了解过。下面重点要开始今天的内容了,介绍几种常用的屏幕适配的方案。

一、自定义像素适配

核心思想:自定义View,以一个特定宽度尺寸的设备为参考,在View的加载过程,根据当前设备的实际像素换算出目标像素,再作用在控件上。

举个例子:通常公司的UI设计师一般都是只出一套设计稿,可能是以目前市面上Android设备的主流分辨率为参考,或者直接以iOS为参考,假设她给出的设计稿规范为720x1280,如果是运行在1080x1920的设备上,那么此时我们就需要做屏幕适配了。如果有一个按钮的宽是360px,它运行在720x1280的设备上是占到屏幕宽度的一半,当它运行在1080x1920的设备上时变成了屏幕宽度的1/3,这种情况肯定不是我们想要的,我们需要的是它无论显示在何种设备上都是占到屏幕宽度的1/2,所以我们就需要进行计算了,计算出来一个相对应的缩放比例,其实也很好计算,即:1080/720x360=540px,所以我们需要在1080的设备上将按钮的宽度调整为540个像素才能使两边显示的效果一致。

OK,下面我们通过自定义ViewGroup来实现屏幕适配,通过继承系统控件比如LinearLayout、RelativeLayout等控件,在onMesure()方法中通过缩放比例来重新计算各个子View的宽高从而实现屏幕的适配。

首先我们新建一个工具类,来获取屏幕的宽高并且计算出缩放比例:

public class ScreenUtil {

    private static ScreenUtil ScreenUtil;

    //这里是设计稿参考宽高
    private static final float STANDARD_WIDTH = 1080;
    private static final float STANDARD_HEIGHT = 1920;

    //这里是屏幕显示宽高
    private int mDisplayWidth;
    private int mDisplayHeight;

    private ScreenUtil(Context context){
        //获取屏幕的宽高
        if(mDisplayWidth == 0 || mDisplayHeight == 0){
            WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
            if (manager != null){
                DisplayMetrics displayMetrics = new DisplayMetrics();
                manager.getDefaultDisplay().getMetrics(displayMetrics);
                if (displayMetrics.widthPixels > displayMetrics.heightPixels){
                    //横屏
                    mDisplayWidth = displayMetrics.heightPixels;
                    mDisplayHeight = displayMetrics.widthPixels;
                }else{
                    mDisplayWidth = displayMetrics.widthPixels;
                    mDisplayHeight = displayMetrics.heightPixels - getStatusBarHeight(context);
                }
            }
        }

    }

    //获取状态栏的高度
    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 static ScreenUtil getInstance(Context context){
        if (ScreenUtil == null){
            ScreenUtil = new ScreenUtil(context.getApplicationContext());
        }
        return ScreenUtil;
    }

    //获取水平方向的缩放比例
    public float getHorizontalScale(){
        return mDisplayWidth / STANDARD_WIDTH;
    }

    //获取垂直方向的缩放比例
    public float getVerticalScale(){
        return mDisplayHeight / STANDARD_HEIGHT;
    }

}

接着我们来实现自定义的ViewGroup,这里继承自系统的控件,在onMesure()方法中重新计算各个子控件的大小:

public class PixelLinearLayout extends LinearLayout {

    //防止二次测量
    private boolean flag;

    public PixelLinearLayout(Context context) {
        super(context);
    }

    public PixelLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public PixelLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (!flag) {
            float scaleX = ScreenUtil.getInstance(getContext()).getHorizontalScale();
            float scaleY = ScreenUtil.getInstance(getContext()).getVerticalScale();

            int count = getChildCount();
            for (int i = 0; i < count; 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);
            }
            flag = true;
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

这里为了防止二次测量,所以加了一个flag标志位,让计算的代码只执行一次。这里我们是继承自线性布局的,在实际使用过程中你可以根据需求去进行扩展,比如再实现RelativeLayout、FrameLayout等控件。

由于咱们的设计稿也就是标准宽高是1080x1920,所以我们可以在布局文件中添加控件,在不同的设备上运行来看效果。注意:布局文件中控件的大小填入的单位需要使用px,因为我们获取的屏幕尺寸单位是以像素为单位的:

<?xml version="1.0" encoding="utf-8"?>
<com.jarchie.androidui.screenadapter.pixel.PixelLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="540px"
        android:layout_height="200px"
        android:layout_marginTop="30dp"
        android:background="@color/colorAccent"
        android:gravity="center"
        android:text="自定义像素适配"
        android:textColor="#fff" />

</com.jarchie.androidui.screenadapter.pixel.PixelLinearLayout>

由于手里没那么多真机,所以就简单的使用模拟器来进行测试了,这里我分别启动了1080x1920和720x1280的两个模拟器:

在1080x1920和720x1280的两台设备上的效果如下图所示,都是占了屏幕宽度的一半:

二、百分比布局适配

核心思想:以父容器尺寸作为参考,在View的加载过程,根据当前父容器实际尺寸换算出目标尺寸,再作用在View上。

使用百分比布局适配,我们无需知道设计稿的尺寸,我们只需要知道控件占用屏幕宽高的比例即可。

首先需要在build.gradle文件中添加百分比布局的依赖:

//谷歌百分比布局
implementation 'com.android.support:percent:28.0.0'

接着在布局文件中来使用百分比布局,在其中放置一个TextView宽高我们设置各占屏幕的一半,注意这里需要添加命名空间,百分比布局的属性都需要通过app:xxx来设置,如果你不使用命名空间,那么它就和普通的布局没有任何区别,它只是对普通的布局进行了扩充:

<?xml version="1.0" encoding="utf-8"?>
<android.support.percent.PercentRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/colorAccent"
        android:gravity="center"
        android:text="百分比布局适配"
        android:textColor="#fff"
        app:layout_widthPercent="50%"
        app:layout_heightPercent="50%"/>

</android.support.percent.PercentRelativeLayout>

效果如下:

这个就是谷歌官方推出的一个百分比布局适配的库,现在使用好像是会划横线告诉你它已经过时了,不过依然不影响你使用,如果你确实看着不爽,那么你可以自己实现这样一套百分比布局适配的解决方案,其实还是自定义ViewGroup。

这里简单说一下思路,具体的代码就不写了,大家可以参考谷歌官方的百分比布局来写:

  • 新建PercentLayout继承自系统控件RelativeLayout,对系统控件进行扩展
  • 在values文件夹下新建attrs.xml文件,编写自定义属性,就是类似于谷歌官方的app:layout_widthPercent这种形式的
  • 在PercentLayout类中新建LayoutParams这个布局属性内部类,继承自RelativeLayout.LayoutParams这个类,保证原有属性可用,然后解析上面自定义的属性
  • 在onMesure()方法中进行布局属性的获取以及宽高的二次计算

三、修改系统density适配

核心思想:修改density、scaleDensity、densityDpi值,直接更改系统内部对于目标尺寸而言的像素密度。

关于density、scaleDensity、densityDpi这些概念以及它们之间的关系,我找了两篇文章给大家做参考:

《Android:sp与dp(densityDpi与scaledDensity)》

《dpi 、 dip 、分辨率、屏幕尺寸、px、density 关系以及换算》

接下来我们来看看在代码中究竟该如何实现呢?

首先,来写一个工具类,我们给出一个参考设备的宽度,以及屏幕密度和字体缩放比例,然后获取到当前屏幕的显示信息,来计算出目标值density、scaleDensity和densityDpi,并将目标值替换原有的屏幕显示信息:

public class Density {

    private static final float  WIDTH = 360;//参考设备的宽,单位是dp 360 / 2 = 180

    private static float appDensity;//表示屏幕密度
    private static float appScaleDensity; //字体缩放比例,默认appDensity

    public static void setDensity(final Application application, Activity activity){
        //获取当前app的屏幕显示信息
        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, scaleDensity, densityDpi
        float targetDensity = displayMetrics.widthPixels / WIDTH; // 1080 / 360 = 3.0
        float targetScaleDensity = targetDensity * (appScaleDensity / appDensity);
        int targetDensityDpi = (int) (targetDensity * 160);

        //替换Activity的density, scaleDensity, densityDpi
        DisplayMetrics dm = activity.getResources().getDisplayMetrics();
        dm.density = targetDensity;
        dm.scaledDensity = targetScaleDensity;
        dm.densityDpi = targetDensityDpi;
    }
}

上面的代码中我们还添加了系统设置中字体变化前后的一个监听。

OK,一个工具类就可以搞定啦,实际项目中使用时,由于我们的页面肯定不止一个,所以通常情况下你可以使用如下两种方式来进行替换:

①、全局Application

自定义Application类,在onCreate()方法中监听Activity创建的回调,同时别忘记在Manifest.xml清单文件中设置我们自己的Application类:

public class App extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                Density.setDensity(App.this, activity);
            }

            @Override
            public void onActivityStarted(Activity activity) {

            }

            @Override
            public void onActivityResumed(Activity activity) {

            }

            @Override
            public void onActivityPaused(Activity activity) {

            }

            @Override
            public void onActivityStopped(Activity activity) {

            }

            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {

            }

            @Override
            public void onActivityDestroyed(Activity activity) {

            }
        });
    }
}

②、定义基类BaseActivity

定义BaseActivity在该类中统一处理,注意:设置的代码必须写在setContentView之前:

public class BaseActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Density.setDensity(getApplication(),this);
    }
}

public class MainActivity extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_screen_adapter_layout);
    }
}

接着,我们来做个测试,由于我们设定的参考设备的宽是360dp,所以假设我们想让控件占用屏幕的一半,可以直接在布局文件中设置宽度为180dp即可:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/text1"
        android:layout_width="180dp"
        android:layout_height="180dp"
        android:text="左上"
        android:gravity="center"
        android:textColor="#fff"
        android:textSize="18sp"
        android:background="@color/colorAccent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:layout_width="180dp"
        android:layout_height="180dp"
        android:text="右下"
        android:gravity="center"
        android:textColor="#fff"
        android:textSize="18sp"
        android:background="@color/colorAccent"
        app:layout_constraintLeft_toRightOf="@id/text1"
        app:layout_constraintTop_toBottomOf="@id/text1" />

</android.support.constraint.ConstraintLayout>

运行的效果如下:

实际项目中推荐使用这种方案进行屏幕适配,使用方式简单,侵入性低,可以适配三方库和系统控件,并且也不会有性能损耗,其实这也就是今日头条适配方案,相信大家也都听说过这个方案了,当时出来的时候火了一段时间,我特地去把原文翻出来🤲双手奉上:

字节跳动技术团队:《一种极低成本的Android屏幕适配方式》

我这里还为大家准备了另一种类似的解决方案,推荐阅读:《Android 屏幕适配终结者》

四、刘海屏适配

4.1、谷歌官方适配策略

Android官方9.0刘海屏适配策略

  1. 如果非全屏模式(有状态栏),则app不受刘海屏的影响,刘海屏的高就是状态栏的高
  2. 如果全屏模式,app未适配刘海屏,系统会对界面做特殊处理,竖屏向下移动,横屏向右移动

针对上面说的这两种情况,我们通过一个实际的项目来看下效果:

首先将模拟器的开发者选项打开,在开发者模式下找到模拟具有凹口的显示屏,选择长型显示屏凹口,这样就把我们的模拟器调整为了具有刘海屏的显示器:

  

然后准备一张图片,然后编写布局文件,我们让图片横向竖向都充满屏幕:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerInParent="true"
        android:scaleType="fitXY"
        android:src="@drawable/zly" />
</RelativeLayout>

然后在Activity代码中什么都不做,直接启动这个页面。第一种情况运行的结果如下图所示,图片在下方填充,上方状态栏和刘海屏有重叠,这种效果也就是默认会出现的效果:

第二种情况是全屏模式,我们先来考虑第一种应用场景:app某个详情页面的某张图片点击查看大图,一般都是跳转到新的页面让它全屏展示,这种情况该如何做?

首先我们需要将屏幕设置为全屏:

//设置全屏,注意这几行代码需要在setContentView之前执行
requestWindowFeature(Window.FEATURE_NO_TITLE);
Window window = getWindow();
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);

setContentView(R.layout.activity_screen_adapter_layout);

效果如下图所示:

在屏幕上方出现了一条黑边,这是系统为它做的默认的处理,表示如果当前界面为全屏模式,它会将内容区域向下移动,上面这条黑边就会空出来,此时我们就需要对屏幕进行适配了,我们需要将内容区域延伸进刘海区域。

接着来看谷歌官方9.0对它的处理,主要用到的是WindowManager.LayoutParams类里面的LayoutInDisplayCutoutMode这个值,它是Android9.0之后对刘海屏支持的属性,具体都有哪些值我们来看一下:

//让内容区域延伸进刘海
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);

我们设置这个值允许内容延伸进刘海,结果如下图所示:

这里上面状态栏变成了白色,有人会说了你这内容区还是在下方啊,也没见它延伸到刘海中啊?事实上它已经延伸进去了,这个白色是因为顶层布局的背景是白色的,所以我们还需要再做一步操作,将当前页面设置为沉浸式模式:

//设置成沉浸式
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);

设置完成之后运行的效果就是我们想要的了:

在实际项目开发中,对于官方的Android9.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){
                if (displayCutout.getBoundingRects() != null && displayCutout.getBoundingRects().size() > 0 && displayCutout.getSafeInsetTop() > 0){
                    return true;
                }
            }
        }
        return false; 
    }

OK,这样一种场景的刘海屏适配我们就搞定啦!

别急着走,此时如果我们把xml布局中的图片给换掉,我们换成一个文本,让它居中显示,请问会出现什么问题?

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/content">

    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="50dp"
        android:layout_centerHorizontal="true"
        android:background="@color/colorAccent"
        android:gravity="center"
        android:text="Hello Android!"
        android:textColor="#fff" />
</RelativeLayout>

没错,它就变成了这个样子:

这种情况很明显,我们的TextView被刘海区域给挡住了,所以此时就不能只是简单的把内容区域延伸进刘海了,解决方案一般有两种:

①、UI在出设计稿的时候尽量规避这种情况,不要把内容放到这个区域,这种方案以实际公司业务为准,这里不多说了;

②、通过代码把内容向下移动,获取刘海屏的高度,让内容下移对应的高度,一般情况下,刘海的高度等于状态栏的高度,所以我们可以直接取状态栏的高度。

对于②我们来实际操作一下,首先获取刘海的高度:

    //通常情况下,刘海的高就是状态栏的高
    public int heightForDisplayCutout(){
        int resID = getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resID > 0){
            return getResources().getDimensionPixelSize(resID);
        }
        return 96;
    }

然后将内容下移对应的高度:

//将内容区域下移刘海的高度
TextView text= findViewById(R.id.text);
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) text.getLayoutParams();
layoutParams.topMargin = heightForDisplayCutout();
text.setLayoutParams(layoutParams);

或者通过父容器去设置它的Padding:

RelativeLayout layout = findViewById(R.id.content);
layout.setPadding(layout.getPaddingLeft(), heightForDisplayCutout(), layout.getPaddingRight(), layout.getPaddingBottom());

OK,上面这些就是对官方刘海屏的一个适配的过程了,我们再来总结一下操作步骤:

  • 判断手机是否有刘海
  • 设置是否让内容区域延伸进刘海
  • 设置控件是否避开刘海区域
  • 获取刘海的高度

4.2、国内定制ROM适配策略

在实际项目开发过程中,我们需要适配的手机大多都是国内的一些手机厂商他们的产品,比如:华为、小米、OPPO、VIVO...等等,由于国内的手机系统一般都是各家厂商定制的ROM,所以适配这些手机我们需要去针对性的适配,下面为大家整理了国内四大厂商的官方文档地址,大家可以对照着参考官方文档去做适配:

这里给大家提供了一个工具类,方便调用:

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");
        }
    }

    /****************************************OPPO手机***********************************************/
    /**
     * 判断该 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判断是否有刘海, 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;
        }
    }
}

OK,到了这里今天的内容就说的差不多了,咱们下期再会!

祝:工作顺利!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值