How to handle notch(display cutout) in android API

2019-06-16 19:11发布

Android added notch support on API 28, but how to handle it on devices running API 27 (Honor 10, Huawei P20, etc.) ?

I was trying to use DisplayCutoutCompat but I was not able to create an instance of it since documentation does not really point out how create one.

How to create the constructor parameter values: Rect safeInsets, List<Rect> boundingRects?

I also looked into the source code of the constructor, which is a bit confusing to me:

public DisplayCutoutCompat(Rect safeInsets, List<Rect> boundingRects) {
        this(SDK_INT >= 28 ? new DisplayCutout(safeInsets, boundingRects) : null);
    }

This will always return null on devices running API < 28. Thank you in advance.

3条回答
Summer. ? 凉城
2楼-- · 2019-06-16 19:28

Google provided notch related APIs in Android P. Devices with notch and API version lower than P implemented their own notch APIs.You can consult the APIs from device specified documentation.

Also I did not see creation of DisplayCutoutCompat instance in official documentation, but you can create DisplayCutout as follow:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            DisplayCutout displayCutout = getWindow().getDecorView().getRootWindowInsets().getDisplayCutout();
}
查看更多
对你真心纯属浪费
3楼-- · 2019-06-16 19:40

I had similar issue, and had to use reflection to access what I need. My problem was that I had some calculations depending on screen size and while not accessing the notch space, the calculations were wrong and code didn't work well.

    public static final String CLASS_DISPLAY_CUTOUT = "android.view.DisplayCutout";
    public static final String METHOD_GET_DISPLAY_CUTOUT = "getDisplayCutout";
    public static final String FIELD_GET_SAFE_INSET_TOP = "getSafeInsetTop";
    public static final String FIELD_GET_SAFE_INSET_LEFT = "getSafeInsetLeft";
    public static final String FIELD_GET_SAFE_INSET_RIGHT = "getSafeInsetRight";
    public static final String FIELD_GET_SAFE_INSET_BOTTOM = "getSafeInsetBottom";


    try {
            WindowInsets windowInsets = activity.getWindow().getDecorView().getRootWindowInsets();
            if (windowInsets == null) {
                return;
            }
            Method method = WindowInsets.class.getMethod(METHOD_GET_DISPLAY_CUTOUT);
            Object displayCutout = method.invoke(windowInsets);
            if (displayCutout == null) {
                return;
            }
            Class clz = Class.forName(CLASS_DISPLAY_CUTOUT);
            int top = (int) clz.getMethod(FIELD_GET_SAFE_INSET_TOP).invoke(displayCutout);
            int left = (int) clz.getMethod(FIELD_GET_SAFE_INSET_LEFT).invoke(displayCutout);
            int right = (int) clz.getMethod(FIELD_GET_SAFE_INSET_RIGHT).invoke(displayCutout);
            int bottom = (int) clz.getMethod(FIELD_GET_SAFE_INSET_BOTTOM).invoke(displayCutout);
            Rect rect = new Rect(left, top, right, bottom);

        } catch (Exception e) {
            Log.e(TAG, "Error when getting display cutout size");
        }
查看更多
Fickle 薄情
4楼-- · 2019-06-16 19:42

So you want to handle notch(display cutout) in android API lower than 28. That's horrible because different manufactures has different implementations. Nevertheless, all use Java reflection to get notch information. Factory design pattern should be used here.

interface ICutout {
    public boolean hasCutout();

    public Rect[] getCutout();
}
  1. Huawei display cutout

    private static class HuaweiCutout implements ICutout {

    private Context context;
    public HuaweiCutout(@NonNull Context context) {
        this.context = context;
    }
    
    @Override
    public boolean hasCutout() {
        try {
            ClassLoader classLoader = context.getClassLoader();
            Class class_HwNotchSizeUtil = classLoader.loadClass("com.huawei.android.util.HwNotchSizeUtil");
            Method method_hasNotchInScreen = class_HwNotchSizeUtil.getMethod("hasNotchInScreen");
            return (boolean) method_hasNotchInScreen.invoke(class_HwNotchSizeUtil);
        } catch (Exception e) {
        }
        return false;
    }
    
    @Override
    public Rect[] getCutout() {
        try {
            ClassLoader classLoader = context.getClassLoader();
            Class class_HwNotchSizeUtil = classLoader.loadClass("com.huawei.android.util.HwNotchSizeUtil");
            Method method_getNotchSize = class_HwNotchSizeUtil.getMethod("getNotchSize");
    
            int[] size = (int[]) method_getNotchSize.invoke(class_HwNotchSizeUtil);
            int notchWidth = size[0];
            int notchHeight = size[1];
            int screenWidth = DeviceUtil.getScreenWidth(context);
    
            int x = (screenWidth - notchWidth) >> 1;
            int y = 0;
            Rect rect = new Rect(x, y, x + notchWidth, y + notchHeight);
            return new Rect[] {rect};
        } catch (Exception e) {
        }
        return new Rect[0];
    }
    

    }

  2. Oppo display cutout

    private static class OppoCutout implements ICutout {
    
    private Context context;
    public OppoCutout(@NonNull Context context) {
        this.context = context;
    }
    
    @Override
    public boolean hasCutout() {
        String CutoutFeature = "com.oppo.feature.screen.heteromorphism";
        return context.getPackageManager().hasSystemFeature(CutoutFeature);
    }
    
    @Override
    public Rect[] getCutout() {
        String value = getProperty("ro.oppo.screen.heteromorphism");
        String[] texts = value.split(",:");
        int[] values = new int[texts.length];
    
        try {
            for(int i = 0; i < texts.length; ++i)
                values[i] = Integer.parseInt(texts[i]);
        } catch(NumberFormatException e) {
            values = null;
        }
    
        if(values != null && values.length == 4) {
            Rect rect   = new Rect();
            rect.left   = values[0];
            rect.top    = values[1];
            rect.right  = values[2];
            rect.bottom = values[3];
    
            return new Rect[] {rect};
        }
    
        return new Rect[0];
    }
    

    }

  3. Vivo display cutout

    private static class VivoCutout implements ICutout {
    
    private Context context;
    public VivoCutout(@NonNull Context context) {
        this.context = context;
    }
    
    @Override
    public boolean hasCutout() {
        try {
            ClassLoader clazz = context.getClassLoader();
            Class ftFeature = clazz.loadClass("android.util.FtFeature");
            Method[] methods = ftFeature.getDeclaredMethods();
            for(Method method: methods) {
                if (method.getName().equalsIgnoreCase("isFeatureSupport")) {
                    int NOTCH_IN_SCREEN = 0x00000020;  // 表示是否有凹槽
                    int ROUNDED_IN_SCREEN = 0x00000008;  // 表示是否有圆角
                    return (boolean) method.invoke(ftFeature, NOTCH_IN_SCREEN);
                }
            }
        } catch (Exception e) {
        }
        return false;
    }
    
    @Override
    public Rect[] getCutout() {
        // throw new RuntimeException();  // not implemented yet.
        return new Rect[0];
    }
    

    }

  4. Xiaomi display cutout of Android Oreo, of Android Pie

    private static class XiaomiCutout implements ICutout {
    
    private Context context;
    public XiaomiCutout(@NonNull Context context) {
        this.context = context;
    }
    
    @Override
    public boolean hasCutout() {
        // `getprop ro.miui.notch` output 1 if it's a notch screen.
        String text = getProperty("ro.miui.notch");
        return text.equals("1");
    }
    
    @Override
    public Rect[] getCutout() {
        Resources res = context.getResources();
        int widthResId = res.getIdentifier("notch_width", "dimen", "android");
        int heightResId = res.getIdentifier("notch_height", "dimen", "android");
        if(widthResId > 0 && heightResId > 0) {
            int notchWidth = res.getDimensionPixelSize(widthResId);
            int notchHeight = res.getDimensionPixelSize(heightResId);
    
            // one notch in screen top
            int screenWidth = DeviceUtil.getScreenSize(context).getWidth();
            int left = (screenWidth - notchWidth) >> 1;
            int right = left + notchWidth;
            int top = 0;
            int bottom = notchHeight;
            Rect rect = new Rect(left, top, right, bottom);
            return new Rect[] {rect};
        }
    
        return new Rect[0];
    }
    

    }

In case some manufactures' not coming up with a getNotchHeight() method, you can just use the status bar's height. Android has guarantee that notch height is at most the status bar height.

public static int getStatusBarHeight(Context context) {
    int statusBarHeight = 0;
    Resources res = context.getResources();
    int resourceId = res.getIdentifier("status_bar_height", "dimen", "android");
    if (resourceId > 0) {
        statusBarHeight = res.getDimensionPixelSize(resourceId);
    }
    return statusBarHeight;
}

For Android Pie and above (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P), you can use system's API to get notch information. Note the window must be attachedActivity#onAttachedToWindow or you will get null DisplayCutout.

DisplayCutout displayCutout = activity.getWindow().getDecorView().getRootWindowInsets().getDisplayCutout();
查看更多
登录 后发表回答