Android 屏幕适配—刘海屏适配

作者: 大虾啊啊啊 | 来源:发表于2020-03-02 16:54 被阅读0次

1、Google官方适配方案

  • 非全屏模式下,刘海屏的高度等于状态栏的高度,此时我们不需要适配刘海屏,
  • 全屏模式下,假如不做特殊适配,那么内容区域会往下移,刘海区域会有一条黑边,需要进行特殊适配

1.1、刘海屏适配的流程:

(1)判断手机厂商
(2)判断是否有刘海屏
(3)获取刘海屏的高度
(4)根据开发需要,做指定的适配。如:将内容区域填充到刘海区域,内容往下移动刘海屏高度距离等等

1.2、 Google官方适配方案示例

package com.example.wangyiyunclass;

import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;

import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.DisplayCutout;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;

public class MainActivity extends Activity {
    private  Button button;
    
    /**
     * 判断是否有刘海屏
     *
     * @param window
     * @return
     */
    private boolean hasDiplayCutout(Window window) {
        DisplayCutout displayCutout;
        View rootView = window.getDecorView();
        WindowInsets insets = null;

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            insets = rootView.getRootWindowInsets();
            if (insets != null) {
                displayCutout = insets.getDisplayCutout();
                if (displayCutout != null) {
                    //判断刘海屏的个数 和高度
                    if (displayCutout.getBoundingRects() != null
                            && displayCutout.getBoundingRects().size() > 0
                            && displayCutout.getSafeInsetTop() > 0) {
                        return true;
                    }
                }
            }

        }
        return false;

    }

    /**
     * 获取刘海屏的高度
     * 一般情况下 状态栏的高度 就是 刘海屏的高度
     *
     * @return
     */
    private int getDisplayCuoutHeight() {
        int resId = getResources().getIdentifier("status_bar_height","dimen","android");
        if(resId>0){
            return getResources().getDimensionPixelSize(resId);
        }
        return 96;
    }



    @RequiresApi(api = Build.VERSION_CODES.P)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //1.设置全屏
        Window window = getWindow();
        // 去掉窗口标题
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        // 隐藏顶部的状态栏
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);

        //2.判断是否有刘海屏
        boolean hasDiplayCutout = hasDiplayCutout(window);
        //如果有刘海屏,则对刘海屏进行适配
        if (hasDiplayCutout) {
            //3.将内容区域延伸进刘海区域
            WindowManager.LayoutParams params = window.getAttributes();
            /**LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT 全屏模式,内容下移,非全屏不受影响
             *
             *LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES 允许内容延伸进刘海区域
             *
             * LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER  不允许内容延伸进刘海区域
             */
            params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
            window.setAttributes(params);
            //4.设置成 沉寖式
            int flags = View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
            int visivility = window.getDecorView().getSystemUiVisibility();
            visivility |= flags;
            window.getDecorView().setSystemUiVisibility(visivility);
        }
        //5.获取刘海屏高度
        int height = getDisplayCuoutHeight();
        setContentView(R.layout.activity_main);
        button = findViewById(R.id.button);
        RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) button.getLayoutParams();
        params.topMargin = height;
        button.setLayoutParams(params);

    }

}

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout

    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/frameLayout"

    tools:context=".MainActivity">
    <ImageView
        android:scaleType="fitXY"
        android:src="@mipmap/image_no_data"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
    
    <Button
        android:id="@+id/button"
        android:background="#F00"
        android:layout_centerHorizontal="true"
        android:layout_width="50dp"
        android:layout_height="50dp"/>
</RelativeLayout>

运行结果,将图片全屏显示,并把内容区域延伸进刘海屏,按钮的位置往下移动的距离,刚好是刘海屏的高度。

2、各个手机厂商的适配

从Google的适配方案我们知道,刘海屏适配只针对全屏的情况下,非全屏的时候,状态栏的高度一般等于刘海的高度不用适配。适配的主要流程基本上是:
(1)判断是否有刘海屏
(2)获取刘海屏的高度
(3)根据开发需要,做指定的适配。如:将内容区域填充到刘海区域,内容往下移动刘海屏高度距离等等
但是不同的厂商可能会存在刘海的高度不等于状态栏高度的情况下等等,所以各大厂商也对刘海的适配提供了API,在我们开发的时候去查阅各大手机厂商的API即可,参考网站
其他手机厂商(华为,小米,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

  • 以下是总结出的一些厂商的核心API:
package com.example.wangyiyunclass;

import android.content.Context;
import android.os.Build;
import android.util.Log;
import android.view.DisplayCutout;
import android.view.View;
import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowManager;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class Utils {

    /**
     * Google 官方判断是否有刘海屏
     *
     * @param window
     * @return
     */
    private boolean hasDiplayCutout(Window window) {
        DisplayCutout displayCutout;
        View rootView = window.getDecorView();
        WindowInsets insets = null;

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            insets = rootView.getRootWindowInsets();
            if (insets != null) {
                displayCutout = insets.getDisplayCutout();
                if (displayCutout != null) {
                    //判断刘海屏的个数 和高度
                    if (displayCutout.getBoundingRects() != null
                            && displayCutout.getBoundingRects().size() > 0
                            && displayCutout.getSafeInsetTop() > 0) {
                        return true;
                    }
                }
            }

        }
        return false;

    }

    /**
     * Google官方  获取刘海屏的高度
     * 一般情况下 状态栏的高度 就是 刘海屏的高度
     *
     * @return
     */
    private int getDisplayCuoutHeight(Context context) {
        int resId = context.getResources().getIdentifier("status_bar_height","dimen","android");
        if(resId>0){
            return context.getResources().getDimensionPixelSize(resId);
        }
        return 96;
    }



    /**
     * 华为手机 是否刘海
     * @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;
        }
    }

}

相关文章

网友评论

    本文标题:Android 屏幕适配—刘海屏适配

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