WindowInsets.getDisplayCutout имеет значение NULL везде, кроме как в onAttachedToWindow в полноэкранном приложении Java - PullRequest
0 голосов
/ 16 апреля 2020

У меня проблемы с getDisplayCutout() в моем полноэкранном Java приложении. Я могу только получить значение DisplayCutout в функции onAttachedToWindow. После того, как эта функция завершится, я никогда не смогу получить ее снова. Код для получения вырезов:

WindowInsets insets = myActivity.getWindow().getDecorView().getRootWindowInsets();
if (insets != null) {
    DisplayCutout displayCutout = insets.getDisplayCutout();
    if (displayCutout != null && displayCutout.getBoundingRects().size() > 0) {
        // we have cutouts to deal with
    }
}

Вопрос

Как я могу надежно получать вырезы на дисплее из любого места кода, в любое время после присоединения к иерархии представлений?

Воспроизвести выпуск

После долгих исследований я сузил проблему до ОЧЕНЬ широкой проблемы с полноэкранными приложениями, и я удивлен, что никто больше об этом не спрашивает. Фактически мы можем игнорировать мое приложение и просто поработать над двумя шаблонными проектами, которые вы можете сделать сами прямо сейчас.

В студии Android я говорю о проектах для телефонов и планшетов, которые называются Basi c Activity и «Полноэкранный режим». Если вы создадите один из них и внесете следующие изменения:

Для Basi c, измените манифест для обработки изменений конфигурации самостоятельно, добавив android:configChanges="orientation|keyboardHidden|screenSize" под тегом Activity следующим образом:

<activity
    android:name=".MainActivity"
    android:label="@string/app_name"
    android:configChanges="orientation|keyboardHidden|screenSize"
    android:theme="@style/AppTheme.NoActionBar">

Теперь для них обоих добавьте в файл упражнения следующие две функции:

@Override
public void onAttachedToWindow() {
    super.onAttachedToWindow();
    WindowInsets insets = getWindow().getDecorView().getRootWindowInsets();
    if (insets != null) {
        DisplayCutout displayCutout = insets.getDisplayCutout();
        if (displayCutout != null && displayCutout.getBoundingRects().size() > 0) {
            // we have cutouts to deal with
        }
    }
}

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    WindowInsets insets = getWindow().getDecorView().getRootWindowInsets();
    if (insets != null) {
        DisplayCutout displayCutout = insets.getDisplayCutout();
        if (displayCutout != null && displayCutout.getBoundingRects().size() > 0) {
            // we have cutouts to deal with
        }
    }
}

Это все, что вам нужно сделать, чтобы воспроизвести эту проблему. Я запускаю это на симуляторе для Pixel 3 на API Q, на котором я включил имитацию вырезов и выбрал ОБА (так что есть вырез внизу и сверху)

Теперь, если вы остановите точку на линии, где мы пытаемся получить вырез изображения (DisplayCutout displayCutout = insets.getDisplayCutout();), вы увидите, что в приложении Basi c оно работает при запуске и при изменении ориентации, но в полноэкранном приложении оно работает только на при запуске.

На самом деле в моем приложении я тестировал, используя следующий код в onAttachedToWindow:

@Override
public void onAttachedToWindow() {
    super.onAttachedToWindow();
    // start a thread so that UI thread execution can continue
    WorkerThreadManager.StartWork(new WorkerCallback() {
        @Override
        public void StartWorkSafe() {
            // run the following code on the UI thread
            XPlatUtil.RunOnUiThread(new SafeRunnable() {
                public synchronized void RunSafe() {
                    WindowInsets insets = myActivity.getWindow().getDecorView().getRootWindowInsets();
                    if (insets != null) {
                        DisplayCutout displayCutout = insets.getDisplayCutout();
                        if (displayCutout != null && displayCutout.getBoundingRects().size() > 0) {
                            // we have cutouts to deal with
                        }
                    }
                }
            });
        }
    });
}

Этот код запускает поток, так что функция onAttachedToWindow может завершить sh Бег; но поток немедленно отправляет выполнение обратно в поток пользовательского интерфейса, чтобы проверить вырезы.

Задержка между функцией onAttachedToWindow, завершающей ее выполнение, и моей проверкой кода для displayCutout должна быть в порядке наносекунд, но вырезы немедленно недоступны.

Есть идеи? Это как-то ожидается?

Без доступа к вырезами при смене ориентации у меня нет другого выхода, кроме как записать самую большую вставку (сверху или снизу на портрете, так как длинные края не могут иметь их), и применить ее к обоим вверху и внизу в портретной ориентации или влево и вправо в альбомной ориентации.

Это связано с тем, что в android я не могу найти способ проверить, КАКОЙ тип ландшафта в настоящее время активен (например, верхняя часть телефона включена слева или справа). Если бы я мог проверить это, я мог бы, по крайней мере, применить вкладку на краю телефона там, где это необходимо.

1 Ответ

0 голосов
/ 17 апреля 2020

Я выяснил кучу вещей, которые могут помочь другим людям. Сначала я отвечу на исходный вопрос, затем объясню, почему он работает, с некоторыми альтернативами для людей, борющихся с той же проблемой, с которой я столкнулся: Как правильно отобразить полноэкранное приложение на телефоне с вырезами почтовый ящик моего приложения . ПРИМЕЧАНИЕ. Все мои макеты выполнены программно (у меня даже нет файлов xml в каталоге макетов). Это было сделано по кросс-платформенным причинам, и, возможно, именно поэтому у меня возникла эта проблема, а другие нет.

OP Ответ

Используйте следующий код для получения вырезов на дисплее (с approriate null проверяет):

<activity>.getWindowManager().getDefaultDisplay().getCutout();

В моем тестировании это вернуло правильные вырезы в onConfigurationChanged и не равно нулю при вызове за пределами onAttachedToWindow (если, конечно, есть вырезы) .

Объяснено

getWindow().getDecorView().getRootWindowInsets().getDisplayCutout() всегда имеет значение NULL, если доступ осуществляется за пределами функции onAttachedToWindow, когда ваше приложение полноэкранное; Я не нашел способ обойти это. Если вы выводите свое приложение из полноэкранного режима следующим образом:

rootView.setSystemUiVisibility(0
            | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);

Вы получите значение из getDisplayCutout при вызове его извне onAttachedToWindow. Это, конечно, не решение. Хуже того, значение, которое вы получаете от этого вызова функции, когда оно внутри onConfigurationChanged, в большинстве случаев кажется неправильным ; он устарел (показывает расположение вырезов до изменения ориентации) и иногда полностью пропускает вырез (при наличии нескольких вырезов), что приводит к неправильным значениям safeInsets.

Полезные наблюдения

Я обнаружил некоторые полезные вещи, которые нужно знать при попытке решить эту проблему, которые могут помочь другим людям, которые пытаются разобраться с вырезами. В моем сценарии у меня есть полноэкранное приложение, и я не хочу добавлять сложность, пытаясь использовать весь экран, и мне нужно настроить макет в зависимости от размера и расположения выреза. Идеальная обработка вырезов - это гораздо больше усилий, чем того стоит для меня как отдельного разработчика, работающего над приложением.

Все, что я хочу, - это иметь возможность надежно выкладывать все мои виды программным способом без необходимости работать с вырезами. ; это означает, что все, что мне нужно знать, это размер и положение прямоугольника, в котором можно безопасно разместить мой контент. Это звучит просто, но было затруднено вышеприведенной проблемой, и некоторые странности в почтовом ящике Android содержимого, где используются системные оверлеи и вырезы.

Ниже приведены некоторые советы о том, как это можно сделать с различными результаты.

Получение доступного размера экрана (полноэкранное приложение)

Метод 1

Доступный размер экрана можно получить с помощью

<activity>.getWindowManager().getDefaultDisplay().getSize()

Этот размер отображается в всегда используйте безопасно, с или без вырезов, при использовании LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER для автоматической вставки содержимого, чтобы избежать вырезов.

Хотя, похоже, резервируется место для панели навигации. При установленных следующих флагах:

setSystemUiVisibility(0
    | View.SYSTEM_UI_FLAG_LOW_PROFILE
    | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION // hide nav bar
    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN // hide status bar
    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar
    | View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar
    | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY // if they swipe to show the soft-navigation, hide it again after a while.
);

Появляется функция getSize(), чтобы зарезервировать место для панели навигации в нижней части экрана в портретной ориентации. Если вы используете LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER, то этот размер будет компенсировать вырезы, что означает, что ваш контент может уместиться в предоставленном пространстве, но также будет зарезервировано дополнительное пространство для панели навигации. Если вы используете устройство без вырезов, в нижней части по-прежнему будет зарезервировано место для панели навигации. Это не то, что мне нужно.

Метод 2

Другой метод получения размера экрана - сделать

DisplayMetrics metrics = new DisplayMetrics();
MainActivity.getWindowManager().getDefaultDisplay().getRealMetrics(metrics);

. Это приведет к получению фактического размера экрана. в пикселях, от края до края, игнорируя системные наложения или полосы и вырезы в телефоне. Это выглядит как лучший способ для go для полноэкранного приложения в сочетании с точным и надежным методом определения вырезок. Строка состояния и мягкая навигация будут немного странным образом перекрываться с пустым пространством на краю вашего приложения, когда пользователь проведет пальцем, чтобы показать их, но это, безусловно, лучшее решение, которое я нашел, где я могу избежать попыток фактически обернуть вокруг вырезы с моим макетом.

Используйте LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES, затем используйте вырезы на экране, чтобы уменьшить и переместить ваше root представление.

// Get the actual screen size in pixels
DisplayMetrics metrics = new DisplayMetrics();
MainActivity.getWindowManager().getDefaultDisplay().getRealMetrics(metrics);
int width = metrics.widthPixels;
int height = metrics.heightPixels;
// if there are no cutouts, top and left offsets are zero
int top = 0;
int left = 0;
// get any cutouts
DisplayCutout displayCutout = MainActivity.getWindowManager().getDefaultDisplay().getCutout();
// check if there are any cutouts
if (displayCutout != null && displayCutout.getBoundingRects().size() > 0) {
    // add safe insets together to shrink width and height
    width -= (displayCutout.getSafeInsetLeft() + displayCutout.getSafeInsetRight());
    height -= (displayCutout.getSafeInsetTop() + displayCutout.getSafeInsetBottom());
    // NOTE:: with LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES, we can render on the whole screen
    // NOTE::    and therefore we CAN and MUST set the top/left offset to avoid the cutouts.
    top = displayCutout.getSafeInsetTop();
    left = displayCutout.getSafeInsetLeft();
}

Это метод, который я в конечном итоге использовал, и пока не нашли никаких проблем с ним.

Метод 3

Здесь мы используем тот же метод, что и в # 2 , чтобы определить ширину и высоту, которые у нас есть. , но используя LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER, чтобы Android занимал позицию этого контента. Я думал, что это будет работать нормально, в конце концов, я не заинтересован в рендеринге в вырезанном пространстве Это вызывает странную проблему в портретном режиме - высота, которую android зарезервировано для строки состояния, была больше , чем высота верхнего выреза. Это приводит к тому, что мой код вычисляет, что у меня на больше высоты, чем на самом деле, и в результате мой контент обрезается внизу.

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

Заключение

Использование <activity>.getWindowManager().getDefaultDisplay().getCutout(); чтобы получить вырезы дисплея. После этого просто решите, какой метод использовать, чтобы определить, где находится безопасная область для визуализации.

Я рекомендую метод 2

...