В 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. Заранее спасибо.
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();
}
window.decorView.rootWindowInsets возвращает ноль
У меня была аналогичная проблема, и мне пришлось использовать отражение, чтобы получить доступ к тому, что мне нужно. Моя проблема заключалась в том, что у меня были некоторые вычисления в зависимости от размера экрана, и, хотя я не имел доступа к пространству надреза, вычисления были неправильными, и код не работал хорошо.
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");
}
Не могли бы вы поделиться названиями методов отражения и рассказать мне, как вы их нашли.
Итак, вы хотите обрабатывать вырез (вырез на экране) в Android API ниже 28. Это ужасно, потому что разные производители имеют разные реализации. Тем не менее, все используют Отражение Java для получения информации о метке. Здесь следует использовать Заводской шаблон проектирования.
interface ICutout {
public boolean hasCutout();
public Rect[] getCutout();
}
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];
}}
Оппо вырез дисплея
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];
}}
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];
}}
Вырез в дисплее 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();
Для обработки API ниже 28 может использоваться WindowInsetsCompat. Пример Котлина:
WindowInsetsCompat.toWindowInsetsCompat(window.decorView.rootWindowInsets).displayCutout
До Android 9.0 ОС не поддерживает отображение меток. Вам нужно будет связаться с производителями устройств и попросить их совета относительно их дисплеев с вырезом, работающих под управлением более старых версий Android.