Как обрабатывать выемку (вырез дисплея) в Android API ниже 28?

Android добавил поддержку надрезов в API 28, но как с этим справиться на устройствах с API 27 (Honor 10, Huawei P20 и т. д.)?

Я пытался использовать DisplayCutoutCompat, но мне не удалось создать его экземпляр, поскольку в документации не указано, как его создать.

Как создать значения параметров конструктора: Rect safeInsets, List<Rect> boundingRects?

Я также просмотрел исходный код конструктора, который меня немного сбивает с толку:

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

Это всегда будет возвращать значение null на устройствах с API ‹ 28. Заранее спасибо.


person blade    schedule 08.08.2018    source источник
comment
ОС не поддерживает выемки дисплея до Android 9.0. Вам нужно будет связаться с производителями устройств и попросить у них совета относительно их дисплеев с надрезами, работающих под управлением более старых версий Android.   -  person CommonsWare    schedule 08.08.2018


Ответы (4)


Google предоставил API-интерфейсы, связанные с надрезом, в Android P. Устройства с надрезом и версией API ниже, чем P, реализовали свои собственные API-интерфейсы. Вы можете ознакомиться с API-интерфейсами в документации по конкретному устройству.

Также я не видел создания экземпляра DisplayCutoutCompat в официальной документации, но вы можете создать DisplayCutout следующим образом:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            DisplayCutout displayCutout = getWindow().getDecorView().getRootWindowInsets().getDisplayCutout();
}
person fujino ryougi    schedule 28.08.2018
comment
window.decorView.rootWindowInsets возвращает ноль - person user924; 03.12.2020

Итак, вы хотите обрабатывать выемку (вырез дисплея) в Android API ниже 28. Это ужасно, потому что разные производители имеют разные реализации. Тем не менее, все используют отражение Java для получения информации о метках. Здесь следует использовать шаблон проектирования Factory.

interface ICutout {
    public boolean hasCutout();

    public Rect[] getCutout();
}
  1. Huawei вырез дисплея

    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 отображает вырез

    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 вырез экрана

    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 Android Oreo, из 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];
    }}
    

Если некоторые производители не предлагают метод getNotchHeight(), вы можете просто использовать высоту строки состояния. Android гарантирует, что высота метки не больше высоты строки состояния.

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

Для Android Pie и более поздних версий (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) вы можете использовать системный API для получения информации о метках. Обратите внимание, что окно должно быть прикрепленоActivity#onAttachedToWindow, иначе вы получите нулевой DisplayCutout.

DisplayCutout displayCutout = activity.getWindow().getDecorView().getRootWindowInsets().getDisplayCutout();
person KAlO2    schedule 15.06.2019

У меня была аналогичная проблема, и мне пришлось использовать отражение, чтобы получить доступ к тому, что мне нужно. Моя проблема заключалась в том, что у меня были некоторые расчеты в зависимости от размера экрана, и, хотя я не имел доступа к выемке, расчеты были неправильными, и код не работал должным образом.

    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");
        }
person M.Baraka    schedule 04.12.2018
comment
Можете ли вы поделиться именами методов отражения и рассказать мне, как вы их нашли. - person Shivansh; 30.01.2019

Для обработки API ниже 28 можно использовать WindowInsetsCompat. Пример Котлина:

WindowInsetsCompat.toWindowInsetsCompat(window.decorView.rootWindowInsets).displayCutout

person anton belichenko    schedule 18.08.2020