Android刘海屏适配方案

1 什么是刘海屏

(1)屏幕的正上方居中位置(下图黑色区域)会被挖掉一个孔,屏幕被挖掉的区域无法正常显示内容,这种类型的屏幕就是刘海屏,也有其他叫法:挖孔屏、凹凸屏等等,这里统一按刘海屏命名。
(2)现在市场上的情况来说,“刘海屏”主要分成两类,一类是标准的 Android P Api,另外一类就是厂商在 Android P 以下的系统,做的特殊适配。

2 刘海屏适配方案

2.1 需要适配刘海屏的页面

(1)对于有状态栏的页面:不会受到刘海屏特性的影响,因为刘海屏包含在状态栏中了;
(2)全屏显示的页面,主要适配的是:①启动页;②引导页;③阅读器,重点是阅读器

2.2 刘海屏布局及安全区域说明

在这里插入图片描述

2.3 参考链接

android 兼容所有刘海屏的方案大全

3 刘海屏适配方案接入步骤(共3步)

3.1 升级Version到28(已修改)

android {
    compileSdkVersion 28
    buildToolsVersion "28.0.3"

    defaultConfig {
        minSdkVersion 14
        targetSdkVersion 23
    }
}

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile 'com.android.support:design:28.0.0'
    compile 'com.android.support:appcompat-v7:28.0.0'
    compile 'com.android.support:support-v4:28.0.0'
}

3.2 应用页面设置使用刘海区显示

3.2.1 方案一:在应用的AndroidManifest.xml中增加meta-data属性

此属性不仅可以针对Application生效,也可以对Activity配置生效。弊端:对Application生效,意味着该应用的所有页面,系统都不会做竖屏场景的特殊下移或者是横屏场景的右移特殊处理,也不能针对某页面做特殊处理。

       <!-- Android O刘海适配 start -->
        <!-- 华为 -->
        <meta-data
            android:name="android.notch_support"
            android:value="true" />
        <!-- 华为 -->
        
        <!-- 小米 -->
        <meta-data
            android:name="notch.config"
            android:value="portrait|landscape" />
        <!-- 小米 -->
        <!-- Android O刘海适配 end -->

        <!-- Android全面屏全屏 start -->
        <meta-data
            android:name="android.max_aspect"
            android:value="2.2" />
        <!-- Android全面屏全屏 end -->
3.2.2 方案二:动态增加/清除刘海屏flag,请求扩展到刘海区显示/不扩展到刘海区显示
    /**
     *  NotchManager.listenOnChange()
     * 
     * 动态地增加/清除相关的flag,请求使用刘海区显示/不使用刘海区显示
     */
    private static void listenOnChange(Activity activity) {
        if (mNotchUsable) {
            NotchJudgementUtil.addNotchFlag(activity);
        } else {
            NotchJudgementUtil.clearNotchFlag(activity);
        }
    }
3.2.3 小书亭的方案——动态增加/清除刘海屏(建议)

(1) 在应用的AndroidManifest.xml中增加meta-data属性

 <!-- Android全面屏全屏 start -->
        <meta-data
            android:name="android.max_aspect"
            android:value="2.2" />
 <!-- Android全面屏全屏 end -->

(2)使用NotchManager类中的初始化方法:initNotchListener()调用listenOnChange()(initNotchListener()方法已实现);

3.3 在Activity的父类初始化“NotchManager”和添加setFullScreen()方法(已修改)

public class BaseFragmentActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    protected void onResume() {
        super.onResume();
        // 初始化Android O,P刘海屏适配管理类
        NotchManager.initNotchListener(this);
    }

    /**
     * 因为刘海屏适配的问题,设置Activity全屏有且只可以使用此方法
     */
    public void setFullScreen(boolean fullScreen) {
        // 设置是否全屏
        if (fullScreen) {
            WindowManager.LayoutParams attr = getWindow().getAttributes();
            attr.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN;
            getWindow().setAttributes(attr);
        } else {
            final WindowManager.LayoutParams attrs = getWindow().getAttributes();
            attrs.flags &= (~WindowManager.LayoutParams.FLAG_FULLSCREEN);
            getWindow().setAttributes(attrs);
        }

        // 必须在设置状态栏之后设置,使可以绘制到刘海区域
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            NotchJudgementUtil.setAndroidPDisplayCutoutMode(getWindow(), WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES);

            // 设置SystemUi
            if (fullScreen) {
                View decorView = getWindow().getDecorView();
                int systemUiVisibility = decorView.getSystemUiVisibility();
                systemUiVisibility |= View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
                getWindow().getDecorView().setSystemUiVisibility(systemUiVisibility);
            } else {
                View decorView = getWindow().getDecorView();
                int systemUiVisibility = decorView.getSystemUiVisibility();
                systemUiVisibility &= (~View.SYSTEM_UI_FLAG_FULLSCREEN);
                systemUiVisibility &= (~View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
                systemUiVisibility &= (~View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
                getWindow().getDecorView().setSystemUiVisibility(systemUiVisibility);
            }
        }
    }
}

4 具体的适配策略(已修改)

4.1 适配启动页

4.1.1 在onCreate()调用setFullScreen(true)
/**
 * 在设置全屏的Activity中调用setFullScreen(true)
 */
public class SplashActivity extends BaseFragmentActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);  
        
        // 调用父类的setFullScreen方法设置是否全屏
        setFullScreen(true);
    }
}
4.1.2 启动页添加V28下style主题
<!-- 启动页冷启动适配Android P -->
    <style name="XstSplashTheme" parent="XstAppTheme">
        // default/shortEdges/never
        <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
    </style>

参考链接:Android P 凹口屏支持,打造全面屏体验

4.1.3 新增drawable-xxhdpi-2034x1080资源文件夹放18:9的占位图

在这里插入图片描述
参考链接:全面屏Splash/占位图适配/全面屏/刘海屏笔记

4.2 适配引导页

4.2.1 类似启动页,在onCreate()调用setFullScreen(true)
4.2.2 新增drawable-xxhdpi-2034x1080资源文件夹放18:9的占位图

在这里插入图片描述

4.3 适配阅读器

4.3.1 在onCreate()调用setFullScreen(true)(参考)

(1)在onCreate()设置全屏的Activity中调用setFullScreen(true)

public abstract class BaseReadActivity {
	@Override
    protected void onCreate(Bundle savedInstanceState) {
		// 设置全屏与否
        setFullscreen();
	}
}

(2)小书亭的方案:设置Android P的阅读器不扩展到刘海屏显示

public abstract class BaseReadActivity {
	@Override
    protected void onCreate(Bundle savedInstanceState) {
		/设置全屏与否
        setFullscreen();
	}
}

private void setFullscreen() {
        ReadConfig readConfig = UserSharedPreferences.getInstance().getReadConfig();
        if (readConfig.isFullscreen()) {
            // ......
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                // Android P的阅读器不扩展到刘海屏显示
                NotchJudgementUtil.setAndroidPDisplayCutoutMode(getWindow(), WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER);
            } else {
                setFullScreen(true);
            }

        } else {
             // ......
        }

        // Android O的某个厂商可以动态设置不扩展到刘海区显示
        // Android O的华为手机阅读器不扩展到刘海屏显示
        if (OSJudgementUtil.isHuaWei()) {
            NotchJudgementUtil.clearNotchFlag(this);
        }
    }

jj

4.3.2 竖屏下显示章节名(参考)(已修改)

(1)修改阅读器绘制顶部标题方法

protected void drawTopTitle(Canvas canvas, String title, IReader reader) {
        int left = BaseApplication.getContext().getResources().getDimensionPixelOffset(R.dimen.marginleft_readview_top);
        int height = BaseApplication.getContext().getResources().getDimensionPixelOffset(R.dimen.height_readview_top);

        int[] notchSize = NotchManager.mNotchSize;
        if (notchSize != null && notchSize[0] > 0) {
            // 有刘海+可以拿到尺寸
            int maxWidth = (canvas.getWidth() - notchSize[0] - left) / 2;
            int maxLength = TextCustomUtils.breakText(title, reader.getTopInfoPaint(), maxWidth);
            if (maxLength > 0 && maxLength < title.length()) {
                title = title.substring(0, maxLength - 1) + "…";
            }
        } else {
            if (title.length() >= 20) {
                title = title.substring(0, 20) + "…";
            }
        }

        float baseline = TextCustomUtils.getBaseline(height, reader.getBottomInfoPaint());
        if (reader.isShowChapterName()) {
            // 阅读设置---是否显示章节名
            canvas.drawText(title, left, baseline, reader.getTopInfoPaint());
        }
    }

一加6-系统8.1
(2)小书亭方案:修改阅读器绘制顶部标题方法

protected void drawTopTitle(Canvas canvas, String title, IReader reader) {
        // ......
        if (notchSize != null && notchSize[0] > 0 && Build.VERSION.SDK_INT < Build.VERSION_CODES.P
                && !(OSJudgementUtil.isXiaoMi() || OSJudgementUtil.isHuaWei() || OSJudgementUtil.isGoogle())) {
            // 有刘海+可以拿到尺寸+低于Android P+需要计算刘海宽度的手机厂商
          
        } else {
            
        }
        // ......
    }

jj

4.3.2 横屏下绘制边距(参考)(已修改)

(1)将阅读器的内容绘制距离手机左边"状态栏高度"

   private void setContentMarginLeft() {
        if (NotchManager.getNotchAble() && mReadConfig.getScrrenOrientation() == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
            mReadView.setContentMarginLeft((int) DeviceUtils.getStatusBarHeight(mActivity));
        } else {
            mReadView.setContentMarginLeft(0);
        }
    }
@Override
    public int getContentMarginLeft() {
        if (mPaddingLeft == 0) {
            mPaddingLeft = getPaddingLeft();
        }
        return mPaddingLeft;
    }

    @Override
    public void setContentMarginLeft(int left) {
        mPaddingLeft = getPaddingLeft() + left;
    }

在这里插入图片描述
(2)小书亭方案:将阅读器的内容绘制距离手机左边"状态栏高度"

private void setContentMarginLeft() {
        if (NotchManager.isNeedPaddingLeft(mReadConfig.getScrrenOrientation())) {
            mReadView.setContentMarginLeft((int) DeviceUtils.getStatusBarHeight(mActivity));
        } else {
            mReadView.setContentMarginLeft(0);
        }
    }
4.3.3 如果旋转屏幕时如果需要动态修改是否扩展到刘海区域(参考)
 /**
     * 小米/华为:旋转屏幕时动态的去清除或加上相关的flag
     *
     * @param orientation
     */
    private void notchListenScreenOrientation(boolean orientation) {
        if (NotchManager.isNeedNotchOrientation()) {
            NotchManager.listenScreenOrientation(this, orientation);
        }
    }

5 Android O,P刘海屏源码

5.1 Android O,P刘海屏适配管理类(必须导入)(已修改)

public class NotchManager {

    /**
     * 是否有刘海
     */
    private static boolean mHasNotch = false;
    /**
     * 判断刘海区域是否隐藏
     */
    private static boolean mNotchUsable = true;
    /**
     * 计算获取刘海尺寸:width、height
     */
    public static int[] mNotchSize = new int[2];

    private static boolean isJudgeNotch = false;

    /**
     * 监听虚拟按键的初次判断及同一屏幕方向下的变化
     * <p>
     * 结果存储在静态变量
     */
    public static void initNotchListener(final Activity activity) {
        if (ActivityUtils.assertActivityDestroyed(activity)) {
            return;
        }

        if (isJudgeNotch) {
            listenNotchOnChange(activity);
            return;
        }

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
            isJudgeNotch = true;
            mHasNotch = false;
            mNotchUsable = false;
            mNotchSize = new int[2];
            return;
        }

        NotchJudgementUtil.assertNotch(activity, new NotchJudgementUtil.NotchJudgementListener() {
            @Override
            public void end(boolean hasNotch, boolean notchUsable, int[] notchSize) {
                isJudgeNotch = true;
                mHasNotch = hasNotch;
                mNotchUsable = notchUsable;
                mNotchSize = notchSize;

                if (mHasNotch) {
                    listenOnChange(activity);
                }
            }
        });
    }

    /**
     * true:有刘海、且刘海区域显示
     *
     * @return
     */
    private static boolean getNotchAble() {
        return mHasNotch && mNotchUsable;
    }

    /**
     * true:有刘海
     *
     * @return
     */
    private static boolean getHasNotch() {
        return mHasNotch;
    }

    /**
     * true:刘海区域显示
     *
     * @return
     */
    private static boolean getNotchUsable() {
        return mNotchUsable;
    }

    /**
     * 监听到刘海区域是否隐藏发生变化。如果是,需要做如重新布局等相关操作
     *
     * @param activity
     */
    private static void listenNotchOnChange(Activity activity) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || !mHasNotch) {
            return;
        }

        boolean notchUsable = NotchJudgementUtil.isNotchUsable(activity);
        if (mNotchUsable != notchUsable) {
            mNotchUsable = notchUsable;
        }

        listenOnChange(activity);
    }

    /**
     * 动态地增加/清除相关的flag,请求扩展到刘海区显示/不扩展到刘海区显示
     *
     * @param activity
     */
    private static void listenOnChange(Activity activity) {
        if (mNotchUsable) {
            NotchJudgementUtil.addNotchFlag(activity);
        } else {
            NotchJudgementUtil.clearNotchFlag(activity);
        }
    }

    /**
     * 旋转屏幕时动态的去清除或加上相关的flag
     *
     * @param activity
     * @param orientation
     */
    public static void listenScreenOrientation(Activity activity, boolean orientation) {
        if (mNotchUsable && orientation) {
            NotchJudgementUtil.addNotchFlag(activity);
        } else {
            NotchJudgementUtil.clearNotchFlag(activity);
        }
    }
}

以下代码不是必须,根据每个项目的UI需要添加:

public class NotchManager {

    /**
     * 判断是否是:小米
     */
    public static boolean isNeedNotchOrientation() {
        return OSJudgementUtil.isXiaoMi();
    }

    /**
     * 判断是否:在竖屏下的总处理方法 --> 本地/在线阅读器
     *
     * @param orientation
     * @return
     */
    public static boolean isPaddingTop(int orientation) {
        if (orientation != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
            return false;
        }

        // 默认Android P+竖屏不使用刘海区显示
        if (isPPaddingTop()) {
            // 28+的ViVo刘海屏一直扩展到刘海区
            return OSJudgementUtil.isVIVO();
        }

        return isNeedPaddingTop(orientation) || isMiNeedPaddingTop() || isHuaNeedPaddingTop();
    }

    /**
     * 判断是否在刘海屏下方绘制:在竖屏下 && 刘海屏可用 && 其他   ---> 网页阅读器
     *
     * @param orientation
     * @return
     */
    public static boolean isNeedPaddingTop(int orientation) {
        if (orientation != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
            return false;
        }

        // 默认Android P+竖屏不扩展刘海区显示
        if (isOsPPaddingTop()) {
            // P+的ViVo刘海屏一直扩展到刘海区
            return OSJudgementUtil.isVIVO();
        }

        // 非(华为/Google)-(因为华为/Google竖屏不使用刘海区显示)
        return getNotchAble() && !(OSJudgementUtil.isHuaWei() || OSJudgementUtil.isGoogle());
    }

    /**
     * 判断是否:有刘海屏 && Android P+
     *
     * @return
     */
    private static boolean isOsPPaddingTop() {
        return getHasNotch() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
	}

    /**
     * 判断是否:有刘海屏 && 小米(小米隐藏刘海屏时,竖屏“本地/在线阅读器”的对话框按“存在刘海屏”显示,只区分是否有刘海屏)
     *
     * @return
     */
    private static boolean isMiNeedPaddingTop() {
        return getHasNotch() && OSJudgementUtil.isXiaoMi();
    }

    /**
     * 判断是否:刘海屏是否可用 && 华为 && Android 8.1(华为隐藏刘海屏时,竖屏“本地/在线阅读器”的对话框按“存在刘海屏”显示,只区分刘海屏是否可用)
     *
     * @return
     */
    private static boolean isHuaNeedPaddingTop() {
        return getNotchUsable() && OSJudgementUtil.isHuaWei() && Build.VERSION.SDK_INT == Build.VERSION_CODES.O_MR1;
    }

    /**
     * 判断是否在刘海屏左方绘制:在横屏下 && 刘海屏可用 && 其他
     *
     * @param orientation
     * @return
     */
    public static boolean isNeedPaddingLeft(int orientation) {
        if (orientation != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
            return false;
        }

        if (isPPaddingLeft()) {
            // Android P+的ViVo刘海屏一直扩展到刘海区(Android P+其他机型默认不扩展到刘海区,不需要距离左边状态栏高度)
            return OSJudgementUtil.isVIVO();
        }

        // 非(小米/华为/Google),因为小米/华为/Google横屏不扩展刘海区显示
        return getNotchAble() && !(OSJudgementUtil.isXiaoMi() || OSJudgementUtil.isHuaWei() || OSJudgementUtil.isGoogle());
    }

    /**
     * 判断是否:有刘海屏 && Android P+
     *
     * @return
     */
    private static boolean isPPaddingLeft() {
        return getHasNotch() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
    }
}

5.2 Android O,P刘海屏适配工具类(必须导入)

@SuppressWarnings({"SimplifiableIfStatement", "ConstantConditions", "unused", "unchecked"})
public class NotchJudgementUtil {

    public static void assertNotch(final Activity activity, final NotchJudgementListener listener) {
        if (listener == null || ActivityUtils.assertActivityDestroyed(activity)) {
            return;
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            if (activity != null && activity.getWindow() != null) {
                View decorView = activity.getWindow().getDecorView();
                if (decorView != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
                    decorView.getViewTreeObserver().addOnWindowAttachListener(new ViewTreeObserver.OnWindowAttachListener() {
                        @Override
                        public void onWindowAttached() {
                            listener.end(hasNotchInAndroidP(activity), true, getNotchSizeInAndroidP(activity));
                        }

                        @Override
                        public void onWindowDetached() {

                        }
                    });
                    return;
                }
            } else {
                listener.end(false, false, new int[2]);
                return;
            }
        }

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
            listener.end(false, false, new int[2]);
            return;
        }

        if (OSJudgementUtil.isHuaWei()) {
            listener.end(hasNotchInHuaWei(activity), isNotchUsableInHuawei(activity), getNotchSizeInHuaWei(activity));
            return;
        }
        if (OSJudgementUtil.isOPPO()) {
            listener.end(hasNotchInOPPO(activity), isNotchUsableInOPPO(activity), getNotchSizeInOPPO(activity));
            return;
        }
        if (OSJudgementUtil.isVIVO()) {
            listener.end(hasNotchInVIVO(activity), isNotchUsableInVIVO(activity), getNotchSizeInVIVO(activity));
            return;
        }
        if (OSJudgementUtil.isXiaoMi()) {
            listener.end(hasNotchInXiaoMi(activity), isNotchUsableInXiaoMi(activity), getNotchSizeInXiaoMi(activity));
            return;
        }
        listener.end(false, false, new int[2]);
    }


    /**
     * int[0]值为刘海宽度 int[1]值为刘海高度
     */
    private static int[] getNotchSizeInAndroidP(Activity activity) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            if (activity != null && activity.getWindow() != null) {
                View decorView = activity.getWindow().getDecorView();
                if (decorView != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                    WindowInsets windowInsets = decorView.getRootWindowInsets();
                    if (windowInsets != null && windowInsets.getDisplayCutout() != null) {
                        DisplayCutout cutout = windowInsets.getDisplayCutout();
                        return new int[]{DeviceUtils.getScreenWidthInPx(activity) - cutout.getSafeInsetLeft() - cutout.getSafeInsetRight(), cutout.getSafeInsetTop()};
                    }
                }
            }
        }
        return new int[2];
    }

    /**
     * 因为小米获取不到刘海区的size,所以认为不可绘制
     *
     * @param context
     * @return
     */
    private static int[] getNotchSizeInXiaoMi(Context context) {
        return new int[2];
    }

    /**
     * VIVO获取刘海的尺寸
     *
     * @param context
     * @return
     */
    private static int[] getNotchSizeInVIVO(Context context) {
        return new int[]{DeviceUtils.dp2px(context, 100), DeviceUtils.dp2px(context, 27)};
    }

    /**
     * OPPO获取刘海的尺寸
     *
     * @param context
     * @return
     */
    private static int[] getNotchSizeInOPPO(Context context) {
        return new int[]{324, 80};
    }

    /**
     * 华为获取刘海的尺寸
     *
     * @param context
     * @return
     */
    private static int[] getNotchSizeInHuaWei(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;
    }

    @SuppressWarnings("RedundantIfStatement")
    private static boolean hasNotchInAndroidP(Activity activity) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            try {
                if (activity == null || activity.getWindow() == null || activity.getWindow().getDecorView() == null) {
                    return false;
                }

                if (OSJudgementUtil.isVIVO()) {
                    // AndroidP-VIVO的displayCutout为null,默认按有刘海区域处理
                    return true;
                }

                View decorView = activity.getWindow().getDecorView();
                if (decorView != null && decorView.getRootWindowInsets() != null) {
                    DisplayCutout displayCutout = decorView.getRootWindowInsets().getDisplayCutout();
                    if (displayCutout != null) {
                        List<Rect> rectList = displayCutout.getBoundingRects();
                        if (rectList == null || rectList.size() == 0) {
                            return false;
                        } else {
                            return true;
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        return false;
    }

    @SuppressWarnings("unchecked")
    private static boolean hasNotchInVIVO(Context context) {
        boolean ret = false;
        try {
            ClassLoader cl = context.getClassLoader();
            @SuppressLint("PrivateApi") Class feature = cl.loadClass("android.util.FtFeature");
            Method get = feature.getMethod("isFeatureSupport", int.class);
            ret = (boolean) get.invoke(feature, 0x00000020);

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

    private static boolean hasNotchInXiaoMi(Context context) {
        return PropertyUtils.get("ro.miui.notch", "0").equalsIgnoreCase("1");
    }

    private static boolean hasNotchInOPPO(Context context) {
        return context.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism");
    }

    @SuppressWarnings("unchecked")
    private static boolean hasNotchInHuaWei(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;
    }


    /**
     * 刘海区域是否可以隐藏的场景
     *
     * @param activity
     * @return
     */
    public static boolean isNotchUsable(Activity activity) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || ActivityUtils.assertActivityDestroyed(activity)) {
            return false;
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            return isNotchUsableInAndroidP(activity);
        }

        Context context = BaseApplication.getContext();
        if (OSJudgementUtil.isHuaWei()) {
            return isNotchUsableInHuawei(activity);
        }
        if (OSJudgementUtil.isOPPO()) {
            return isNotchUsableInOPPO(activity);
        }
        if (OSJudgementUtil.isVIVO()) {
            return isNotchUsableInVIVO(activity);
        }
        if (OSJudgementUtil.isXiaoMi()) {
            return isNotchUsableInXiaoMi(activity);
        }
        return false;
    }

    /**
     * 默认AndroidP+刘海屏不可用
     *
     * @param activity
     * @return
     */
    private static boolean isNotchUsableInAndroidP(Activity activity) {
        return false;
    }

    /**
     * vivo刘海屏可用
     *
     * @param activity
     * @return
     */
    private static boolean isNotchUsableInVIVO(Activity activity) {
        return true;
    }

    /**
     * oppo刘海屏可用
     *
     * @param activity
     * @return
     */
    private static boolean isNotchUsableInOPPO(Activity activity) {
        return true;
    }

    /**
     * 判断Huawei刘海屏是否可用
     *
     * @param activity
     * @return
     */
    private static boolean isNotchUsableInHuawei(Activity activity) {
        // 0表示“默认”,1表示“隐藏显示区域”
        return Settings.Secure.getInt(activity.getContentResolver(), "display_notch_status", 0) == 0;
    }

    /**
     * 判断XiaoMi刘海屏是否可用
     *
     * @param activity
     * @return
     */
    private static boolean isNotchUsableInXiaoMi(Activity activity) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            // 0表示“默认”,1表示“隐藏显示区域”
            return Settings.Global.getInt(activity.getContentResolver(), "force_black", 0) == 0;
        } else {
            return false;
        }
    }


    /**
     * 在notchUsable=true时,使用刘海区显示(小米和华为动态配置)
     *
     * @param activity
     */
    public static void addNotchFlag(Activity activity) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || ActivityUtils.assertActivityDestroyed(activity)) {
            return;
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            return;
        }

        if (OSJudgementUtil.isHuaWei()) {
            addNotchFlagInHuaWei(activity);
            return;
        }
        if (OSJudgementUtil.isOPPO()) {
            return;
        }
        if (OSJudgementUtil.isVIVO()) {
            return;
        }
        if (OSJudgementUtil.isXiaoMi()) {
            addNotchFlagInXiaoMi(activity);
            return;
        }
    }

    /**
     * 在notchUsable=false时,不使用刘海区显示(小米和华为动态配置)
     *
     * @param activity
     */
    public static void clearNotchFlag(Activity activity) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || ActivityUtils.assertActivityDestroyed(activity)) {
            return;
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            return;
        }

        if (OSJudgementUtil.isHuaWei()) {
            clearNotchFlagInHuaWei(activity);
            return;
        }
        if (OSJudgementUtil.isOPPO()) {
            return;
        }
        if (OSJudgementUtil.isVIVO()) {
            return;
        }
        if (OSJudgementUtil.isXiaoMi()) {
            clearNotchFlagInXiaoMi(activity);
        }
    }

    /**
     * Window级别的控制接口-华为不使用刘海区显示
     *
     * @param activity
     */
    private static void clearNotchFlagInHuaWei(Activity activity) {
        if (activity.getWindow() == null) {
            return;
        }

        WindowManager.LayoutParams layoutParams = activity.getWindow().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, 0x00010000);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Window级别的控制接口-华为使用刘海区显示
     *
     * @param activity
     */
    private static void addNotchFlagInHuaWei(Activity activity) {
        if (activity.getWindow() == null) {
            return;
        }
        WindowManager.LayoutParams layoutParams = activity.getWindow().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, 0x00010000);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Window级别的控制接口-小米不使用刘海区显示
     *
     * @param activity
     */
    private static void clearNotchFlagInXiaoMi(Activity activity) {
        if (activity.getWindow() == null) {
            return;
        }

        // 横竖屏都绘制到耳朵区
        int flag = 0x00000100 | 0x00000200 | 0x00000400;
        try {
            Method method = Window.class.getMethod("clearExtraFlags", int.class);
            method.invoke(activity.getWindow(), flag);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Window级别的控制接口-小米使用刘海区显示
     *
     * @param activity
     */
    private static void addNotchFlagInXiaoMi(Activity activity) {
        if (activity.getWindow() == null) {
            return;
        }

        // 横竖屏都绘制到耳朵区
        int flag = 0x00000100 | 0x00000200 | 0x00000400;
        try {
            Method method = Window.class.getMethod("addExtraFlags", int.class);
            method.invoke(activity.getWindow(), flag);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Window级别的控制接口-Android P动态控制是否使用刘海区显示
     *
     * WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
     * WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
     * WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
     *
     * @param window
     */
    public static void setAndroidPDisplayCutoutMode(Window window, int cutoutMode) {
        if (window == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
            return;
        }

        WindowManager.LayoutParams windowManagerDu = window.getAttributes();
        windowManagerDu.layoutInDisplayCutoutMode =cutoutMode;
        window.setAttributes(windowManagerDu );
    }

    public interface NotchJudgementListener {
        /**
         * 刘海信息回调
         *
         * @param hasNotch
         * @param notchUsable
         * @param notchSize
         */
        void end(boolean hasNotch, boolean notchUsable, int[] notchSize);
    }
}

5.3 辅助工具类(根据需求导入)

(1)系统判断OSJudgementUtil

public final class OSJudgementUtil {

    public static boolean isHuaWei() {
        return "HUAWEI".equalsIgnoreCase(android.os.Build.MANUFACTURER);
    }

    public static boolean isZTE() {
        return "ZTE".equalsIgnoreCase(android.os.Build.MANUFACTURER);
    }

    public static boolean is360() {
        return "360".equalsIgnoreCase(android.os.Build.MANUFACTURER) || "QIKU".equalsIgnoreCase(android.os.Build.MANUFACTURER);
    }

    public static boolean isMeizu() {
        return "Meizu".equalsIgnoreCase(android.os.Build.MANUFACTURER);
    }

    public static boolean isOPPO() {
        return "OPPO".equalsIgnoreCase(android.os.Build.MANUFACTURER);
    }

    public static boolean isVIVO() {
        return "VIVO".equalsIgnoreCase(android.os.Build.MANUFACTURER);
    }

    public static boolean isXiaoMi() {
        return "Xiaomi".equalsIgnoreCase(android.os.Build.MANUFACTURER);
    }

    public static boolean isGoogle() {
        return "Google".equalsIgnoreCase(android.os.Build.MANUFACTURER);
    }
}

(2) 设备相关信息DeviceUtils

public class DeviceUtils {
	/**
     * 获取屏幕宽度(单位:像素)
     *
     * @param context
     * @return
     */
    public static int getScreenWidthInPx(Context context) {
        if (context == null) {
            return 0;
        }

        return context.getResources().getDisplayMetrics().widthPixels;
    }

   /**
     * dp转px
     *
     * @param context
     * @param dp
     * @return
     */
    public static int dp2px(Context context, int dp) {
        if (context == null) {
            return 0;
        }

        float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dp * scale + 0.5f);
    }
}

(3) ActivityUtils

public class ActivityUtils {
    /**
     * @param activity
     * @return true=Activity已经销毁,false=没有销毁
     */
    public static boolean assertActivityDestroyed(Activity activity) {
        if (activity == null) {
            return true;
        }

        WeakReference<Activity> weakReference = new WeakReference<>(activity);
        Activity weakActivity = weakReference.get();
        if (weakActivity == null) {
            return true;
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && weakActivity.isDestroyed()) {
            return true;
        }
        if (weakActivity.isFinishing()) {
            return true;
        }
        return false;
    }
}

(4) PropertyUtils

public class PropertyUtils {
    private static volatile Method set = null;
    private static volatile Method get = null;

    public static void set(String prop, String value) {

        try {
            if (null == set) {
                synchronized (PropertyUtils.class) {
                    if (null == set) {
                        Class<?> cls = Class.forName("android.os.SystemProperties");
                        set = cls.getDeclaredMethod("set", new Class<?>[]{String.class, String.class});
                    }
                }
            }
            set.invoke(null, new Object[]{prop, value});
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }


    public static String get(String prop, String defaultvalue) {
        String value = defaultvalue;
        try {
            if (null == get) {
                synchronized (PropertyUtils.class) {
                    if (null == get) {
                        Class<?> cls = Class.forName("android.os.SystemProperties");
                        get = cls.getDeclaredMethod("get", new Class<?>[]{String.class, String.class});
                    }
                }
            }
            value = (String) (get.invoke(null, new Object[]{prop, defaultvalue}));
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return value;
    }
}

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值