Размытие окон

В Android 12 доступны общедоступные API для реализации эффектов размытия окон, например размытия фона и размытия позади.

Размытие окна или межэкранное размытие используется для размытия экрана за окном. Существует два типа размытия окна, которые можно использовать для создания различных визуальных эффектов:

  • Размытие фона позволяет создавать окна с размытым фоном, имитируя эффект матового стекла.

  • Размытие фона позволяет размыть весь экран за окном (диалоговым окном), создавая эффект глубины резкости.

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

только размытие фона;

a

только размытие фона

Б

размытие сзади и размытие фона;

c

Рисунок 1. Только размытие фона (a), только размытие за объектом (b), размытие фона и за объектом (c)

Размытие работает для всех окон, в том числе когда за окном браузера открыто другое приложение. Этот эффект отличается от размытия, которое размывает контент внутри того же окна. Размытие окон полезно для диалоговых окон, нижних экранов и других плавающих окон.

Реализация

Разработчикам приложений

Чтобы создать эффект размытия, разработчики приложений должны указать радиус размытия. Радиус размытия определяет плотность размытия. Чем больше радиус, тем плотнее размытие. Размытие 0 пкс означает, что размытия нет. Для размытия фона радиус 20 пикселей создает хороший эффект глубины резкости, а радиус 80 пикселей – эффект матового стекла. Не используйте радиус размытия больше 150 пикселей, так как это значительно повлияет на производительность.

Чтобы добиться желаемого эффекта размытия и повысить читаемость, выберите значение радиуса размытия, дополненное полупрозрачным слоем цвета.

Размытие фона

Используйте размытие фона в плавающих окнах, чтобы создать эффект размытого изображения контента, находящегося под окном. Чтобы добавить размытый фон для окна, выполните следующие действия:

  1. Вызовите метод Window#setBackgroundBlurRadius(int), чтобы задать радиус размытия фона. Или в теме окна задайте значение R.attr.windowBackgroundBlurRadius.

  2. Чтобы сделать окно полупрозрачным, задайте для атрибута R.attr.windowIsTranslucent значение true. Размытие рисуется под поверхностью окна, поэтому окно должно быть полупрозрачным, чтобы размытие было видно.

  3. При необходимости вызовите Window#setBackgroundDrawableResource(int), чтобы добавить прямоугольный фон окна с полупрозрачным цветом. Или в теме окна задайте значение R.attr.windowBackground.

  4. Для окна со скругленными углами определите скругленные углы для размытой области, задав ShapeDrawable со скругленными углами в качестве фона окна.

  5. Обработка включенного и отключенного состояния размытия. Подробнее о том, как использовать размытие окон в приложениях…

Размытие фона

Размытие фона размывает весь экран за окном. Этот эффект используется, чтобы привлечь внимание пользователя к содержимому окна, размывая все, что находится на экране за окном.

Чтобы размыть контент за окном, выполните следующие действия:

  1. Добавьте FLAG_BLUR_BEHIND к флагам окна, чтобы включить размытие фона. Или в теме окна задайте значение R.attr.windowBlurBehindEnabled.

  2. Вызовите метод WindowManager.LayoutParams#setBlurBehindRadius, чтобы задать радиус размытия фона. Или в теме окна задайте значение R.attr.windowBlurBehindRadius.

  3. При желании выберите степень затемнения.

  4. Обрабатывать включенное и отключенное состояния размытия. Подробнее о том, как использовать размытие окон в приложениях…

Рекомендации по использованию размытия окон в приложениях

Поддержка размытия окон зависит от следующих факторов:

  • Версия Android. API размытия окон доступны только в Android 12 и более поздних версиях. Проверьте SDK устройства на наличие версии Android.

  • Производительность графики. Устройства с менее мощными графическими процессорами могут не поддерживать размытие окон.

  • Состояние системы. Системный сервер может временно отключить размытие окон во время выполнения, например в режиме экономии заряда батареи, при воспроизведении определенных типов видеоконтента или из-за переопределения разработчиком.

Чтобы ваше приложение было совместимо с разными версиями Android, устройствами и состояниями системы, следуйте приведенным ниже рекомендациям.

  • Добавьте прослушиватель с помощью WindowManager#addCrossWindowBlurEnabledListener, чтобы получать уведомления о включении или отключении размытия окон. Кроме того, с помощью WindowManager#isCrossWindowBlurEnabled можно узнать, включено ли сейчас размытие окон.

  • Реализуйте две версии фона окна, чтобы учесть включенное или отключенное размытие окна.

    Если размытие включено, фон окна должен быть полупрозрачным, чтобы размытие было видно. В этом случае, когда размытие отключено, содержимое окна напрямую перекрывается содержимым нижележащего окна, что делает перекрывающееся окно менее разборчивым. Чтобы избежать такого эффекта, когда размытие окон отключено, адаптируйте интерфейс приложения следующим образом:

    • Чтобы размыть фон, увеличьте альфа-канал фона окна, сделав его более непрозрачным.

    • Чтобы размыть фон, добавьте слой затемнения с более высоким значением.

Пример размытия фона и размытия позади

В этом разделе приведен рабочий пример действия, в котором используется как размытие фона, так и размытие за ним.

В следующем примере MainActivity.java показано диалоговое окно с радиусом размытия фона 20 пкс и радиусом размытия заднего плана 80 пкс. У него закругленные углы, заданные в XML-файле в элементе drawable для фона окна. Он корректно обрабатывает разные версии Android, разные устройства (которые могут не поддерживать размытие окон) и изменения, связанные с включением или отключением размытия во время выполнения. Это гарантирует, что содержимое диалогового окна будет читаемым при любых условиях, за счет регулировки альфа-канала фона окна и степени затемнения окна.

public class MainActivity extends Activity {

    private final int mBackgroundBlurRadius = 80;
    private final int mBlurBehindRadius = 20;

    // We set a different dim amount depending on whether window blur is enabled or disabled
    private final float mDimAmountWithBlur = 0.1f;
    private final float mDimAmountNoBlur = 0.4f;

    // We set a different alpha depending on whether window blur is enabled or disabled
    private final int mWindowBackgroundAlphaWithBlur = 170;
    private final int mWindowBackgroundAlphaNoBlur = 255;

    // Use a rectangular shape drawable for the window background. The outline of this drawable
    // dictates the shape and rounded corners for the window background blur area.
    private Drawable mWindowBackgroundDrawable;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mWindowBackgroundDrawable = getDrawable(R.drawable.window_background);
        getWindow().setBackgroundDrawable(mWindowBackgroundDrawable);

        if (buildIsAtLeastS()) {
            // Enable blur behind. This can also be done in xml with R.attr#windowBlurBehindEnabled
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND);

            // Register a listener to adjust window UI whenever window blurs are enabled/disabled
            setupWindowBlurListener();
        } else {
            // Window blurs are not available prior to Android S
            updateWindowForBlurs(false /* blursEnabled */);
        }

        // Enable dim. This can also be done in xml, see R.attr#backgroundDimEnabled
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
    }

    /**
     * Set up a window blur listener.
     *
     * Window blurs might be disabled at runtime in response to user preferences or system states
     * (e.g. battery saving mode). WindowManager#addCrossWindowBlurEnabledListener allows to
     * listen for when that happens. In that callback we adjust the UI to account for the
     * added/missing window blurs.
     *
     * For the window background blur we adjust the window background drawable alpha:
     *     - lower when window blurs are enabled to make the blur visible through the window
     *       background drawable
     *     - higher when window blurs are disabled to ensure that the window contents are readable
     *
     * For window blur behind we adjust the dim amount:
     *     - higher when window blurs are disabled - the dim creates a depth of field effect,
     *       bringing the user's attention to the dialog window
     *     - lower when window blurs are enabled - no need for a high alpha, the blur behind is
     *       enough to create a depth of field effect
     */
    @RequiresApi(api = Build.VERSION_CODES.S)
    private void setupWindowBlurListener() {
        Consumer<Boolean> windowBlurEnabledListener = this::updateWindowForBlurs;
        getWindow().getDecorView().addOnAttachStateChangeListener(
                new View.OnAttachStateChangeListener() {
                    @Override
                    public void onViewAttachedToWindow(View v) {
                        getWindowManager().addCrossWindowBlurEnabledListener(
                                windowBlurEnabledListener);
                    }

                    @Override
                    public void onViewDetachedFromWindow(View v) {
                        getWindowManager().removeCrossWindowBlurEnabledListener(
                                windowBlurEnabledListener);
                    }
                });
    }

    private void updateWindowForBlurs(boolean blursEnabled) {
        mWindowBackgroundDrawable.setAlpha(blursEnabled && mBackgroundBlurRadius > 0 ?
                mWindowBackgroundAlphaWithBlur : mWindowBackgroundAlphaNoBlur);
        getWindow().setDimAmount(blursEnabled && mBlurBehindRadius > 0 ?
                mDimAmountWithBlur : mDimAmountNoBlur);

        if (buildIsAtLeastS()) {
            // Set the window background blur and blur behind radii
            getWindow().setBackgroundBlurRadius(mBackgroundBlurRadius);
            getWindow().getAttributes().setBlurBehindRadius(mBlurBehindRadius);
            getWindow().setAttributes(getWindow().getAttributes());
        }
    }

    private static boolean buildIsAtLeastS() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S;
    }
}

Чтобы создать для окна закругленные углы, мы определяем фон окна в res/drawable/window_background.xml как ShapeDrawable с закругленными углами радиусом 20 dp следующим образом:

<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
    <corners android:radius="20dp"/>
    <solid android:color="#AAAAAA"/>
</shape>

Размытие окна размывает содержимое окна под действием. Размытое изображение отрисовывается под окном активности, поэтому окно активности должно быть полупрозрачным, чтобы размытие было видно. Чтобы сделать окно полупрозрачным, задайте для атрибута R.attr.windowIsTranslucent в теме действия следующее значение:

<style name="Theme.BlurryDialog" parent="Theme.MaterialComponents.Dialog">
    <item name="android:windowIsTranslucent">true</item>
</style>

Производители оригинального оборудования и партнеры

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

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

  • Убедитесь, что устройство может справиться с дополнительной нагрузкой на графический процессор. Устройства более низкого класса могут не справиться с дополнительной нагрузкой, что приведет к пропуску кадров. Включайте размытие окон только на протестированных устройствах с достаточной мощностью графического процессора.

  • Если вы используете собственный движок рендеринга, убедитесь, что в нем реализована логика размытия. В механизме отрисовки, используемом по умолчанию в Android 12, логика размытия реализована в BlurFilter.cpp.

Убедившись, что устройство поддерживает размытие окон, задайте следующие параметры surface flinger sysprop:

PRODUCT_VENDOR_PROPERTIES += \
       ro.surface_flinger.supports_background_blur=1

Проверка

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

  1. Откройте интерфейс с размытием.

  2. Включить или отключить размытие окон можно с помощью переключателя размытия окон.

  3. Убедитесь, что интерфейс окна размывается и становится четким, как и ожидалось.

Как включить или отключить размытие фона окна

Чтобы проверить, как интерфейс окна отображается с эффектом размытия, включите или отключите его одним из следующих способов:

  • В разделе "Для разработчиков":

    Настройки > Система > Для разработчиков > Аппаратное ускорение отрисовки > Разрешить размытие на уровне окна

  • В терминале на устройстве с root-доступом:

    adb shell wm disable-blur 1 # 1 disables window blurs, 0 allows them

Чтобы проверить, поддерживает ли ваше устройство с Android 12 или более поздней версии размытие окон и включена ли эта функция, запустите команду adb shell wm disable-blur на устройстве с root-доступом.

Устранение неполадок

Ниже приведены рекомендации по устранению неполадок во время проверки.

Размытие не применено

  • Убедитесь, что размытие включено и поддерживается вашим оборудованием. Подробнее о том, как включить или отключить размытие окон…

  • Убедитесь, что вы задали полупрозрачный цвет фона окна. Непрозрачный цвет фона окна скрывает размытую область.

Тестовое устройство не поддерживает размытие окон

  • Протестируйте приложение в эмуляторе Android 12. Инструкции по настройке эмулятора Android приведены в статье Как настроить эмулятор Android. Любое виртуальное устройство Android, созданное с помощью эмулятора, поддерживает размытие окон.

Без скругленных углов

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

Изменение параметра для разработчиков не включает размытие

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

Размытие фона на весь экран, а не в пределах окна

Изменения, внесенные слушателем, не отображаются на экране

  • Обновления прослушивателя могут применяться к старому экземпляру окна. Проверьте, уничтожается ли окно и создается ли оно заново с правильным обновлением прослушивателя.