Rozmycie okna

W Androidzie 12 dostępne są publiczne interfejsy API do implementowania efektów rozmywania okna, takich jak rozmycie tła i rozmycie tła okna.

Rozmywanie okna lub rozmywanie między oknami służy do rozmywania ekranu za danym oknem. Dostępne są 2 typy rozmyć okna, które można wykorzystać do uzyskania różnych efektów wizualnych:

  • Rozmycie tła umożliwia tworzenie okien z rozmytym tłem, co daje efekt zamrożonego szkła.

  • Rozmycie tła umożliwia rozmycie całego ekranu za oknem (dialogowym), tworząc efekt głębi ostrości.

Te 2 efekty możesz stosować oddzielnie lub razem, jak pokazano na rysunku:

tylko rozmycie tła

a

rozmyj tylko tło

b

rozmycie tła i rozmycie tła

c

Rysunek 1. Tylko rozmycie tła (a), tylko rozmycie tła (b), rozmycie tła i rozmycie tła za obiektem (c)

Funkcja rozmycia okna działa w przypadku wszystkich okien, co oznacza, że działa również wtedy, gdy w tle jest inna aplikacja. Ten efekt różni się od efektu rozmycia renderowania, który rozmywa treści w tym samym oknie. Rozmywanie okna jest przydatne w przypadku okien dialogowych, plansz dolnych i pozostałych pływających okien.

Implementacja

Programiści aplikacji

Deweloperzy aplikacji muszą podać promień rozmycia, aby utworzyć efekt rozmycia. Promień rozmycia określa gęstość rozmycia, czyli im większy promień, tym gęstsze rozmycie. Rozmycie równe 0 pikseli oznacza brak rozmycia. Aby rozmyć tło, użyj promienia 20 pikseli, aby uzyskać efekt głębi ostrości, a promień 80 pikseli, aby uzyskać efekt matowego szkła. Unikaj stosowania wartości promieniowania większych niż 150 pikseli, ponieważ może to znacznie wpłynąć na wydajność.

Aby uzyskać pożądany efekt rozmycia i zwiększyć czytelność, wybierz wartość promienia rozmycia uzupełnioną przezroczystą warstwą koloru.

Rozmycie tła

Użyj rozmycia tła w oknach wyseparowanych, aby utworzyć efekt tła okna, czyli rozmyty obraz treści znajdujących się pod oknem. Aby dodać rozmyte tło okna:

  1. Aby ustawić promień rozmywania tła, wywołaj metodę Window#setBackgroundBlurRadius(int). Możesz też w motywie okna ustawić wartość R.attr.windowBackgroundBlurRadius.

  2. Aby okno było półprzezroczyste, ustaw wartość R.attr.windowIsTranslucent na „true”. Rozmycie jest nakładane pod powierzchnią okna, więc okno musi być półprzezroczyste, aby rozmycie było widoczne.

  3. Opcjonalnie wywołaj metodę Window#setBackgroundDrawableResource(int), aby dodać prostokątny zasób rysowania tła okna z przezroczystym kolorem. Możesz też w motywie okna ustawić R.attr.windowBackground.

  4. W przypadku okna z zaokrąglonymi narożnikami określ zaokrąglone narożniki rozmytego obszaru, ustawiając ShapeDrawablezaokrąglonymi narożnikami jako element rysowania tła okna.

  5. Obsługa stanów włączonego i wyłączonego zamglenia. Więcej informacji znajdziesz w sekcji Wskazówki dotyczące używania rozmycia okna w aplikacjach.

Rozmycie tła

Rozmycie tła rozmywałoby cały ekran za oknem. Ten efekt służy do zwrócenia uwagi użytkownika na zawartość okna poprzez rozmycie wszystkiego, co znajduje się na ekranie za oknem.

Aby rozmyć zawartość za oknem, wykonaj te czynności:

  1. Dodaj FLAG_BLUR_BEHIND do flag okien, aby włączyć rozmycie tła. Możesz też w motywie okna ustawić wartość R.attr.windowBlurBehindEnabled.

  2. Aby ustawić rozmycie za obwodem, wywołaj funkcję WindowManager.LayoutParams#setBlurBehindRadius. Możesz też w motywie okna ustawić wartość R.attr.windowBlurBehindRadius.

  3. Opcjonalnie wybierz uzupełniającą wartość wymiaru.

  4. Obsługa stanów włączonego i wyłączonego zamglenia. Więcej informacji znajdziesz w sekcji Wskazówki dotyczące używania rozmycia okna w aplikacjach.

Wskazówki dotyczące używania rozmycia okna w aplikacjach

Obsługa rozmycia okien zależy od tych czynników:

  • Wersja Androida: interfejsy API rozmycia okien są dostępne tylko w Androidzie 12 lub nowszym. Sprawdź wersję Androida w pakiecie SDK urządzenia.

  • Wydajność grafiki: urządzenia z procesorami GPU o mniejszej wydajności mogą nie obsługiwać rozmywania okna.

  • Stan systemu: serwer systemowy może tymczasowo wyłączyć rozmycie okna w czasie działania, na przykład w trybie oszczędzania baterii, podczas odtwarzania niektórych rodzajów treści wideo lub z powodu zastąpienia przez programistę.

Aby zapewnić kompatybilność aplikacji z różnymi wersjami Androida, urządzeniami i stanami systemu, postępuj zgodnie z tymi wytycznymi:

  • Dodaj obiekt WindowManager#addCrossWindowBlurEnabledListener, aby otrzymywać powiadomienia o włączeniu lub wyłączeniu rozmycia okna. Dodatkowo możesz użyć zapytania WindowManager#isCrossWindowBlurEnabled, aby sprawdzić, czy rozmycie okna jest obecnie włączone.

  • Utwórz 2 wersje tła okna, aby uwzględnić włączony lub wyłączony stan rozmywania okna.

    Gdy rozmycie jest włączone, tło okna powinno być półprzezroczyste, aby rozmycie było widoczne. W tym stanie, gdy rozmycie jest wyłączone, zawartość okna bezpośrednio zachodzi na zawartość okna podrzędnego, przez co okna nakładające się na siebie są mniej czytelne. Aby uniknąć takiego efektu, gdy rozmycie okna jest wyłączone, dostosuj interfejs aplikacji w ten sposób:

    • Aby rozmyć tło, zwiększ przezroczystość tła okna, aby było bardziej nieprzejrzyste.

    • Aby rozmyć tło, dodaj warstwę przyciemniającą z większym przyciemnieniem.

Przykład rozmycia tła za obiektem i rozmycia tła

Ta sekcja zawiera działający przykład aktywności, która korzysta zarówno z rozmycia tła, jak i rozmycia tła obiektu.

Przykład MainActivity.java to okno z rozmyciem tła o promie 20 pikseli i z rozmyciem tła o promie 80 pikseli. Ma zaokrąglone rogi zdefiniowane w pliku XML w ramce rysowanej tła okna. Obsługuje ona prawidłowo różne wersje Androida, różne urządzenia (które mogą nie obsługiwać rozmycie okna) oraz włączone lub wyłączone zmiany rozmycie w czasie wykonywania. Za pomocą regulacji przezroczystości tła okna i poziomu przyciemnienia okna zapewnia, że dialog jest czytelny w każdych z tych warunków.

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

Aby utworzyć zaokrąglone rogi okna, w res/drawable/window_background.xml zdefiniujemy tło okna jako ShapeDrawablezaokrąglonymi rogami o promieniu 20 dp w ten sposób:

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

Rozmycie okna zaciera zawartość okna znajdującego się pod aktywnością. Zamglone zdjęcie jest wyświetlane pod tym oknem aktywności, więc okno aktywności musi być półprzezroczyste, aby umożliwić wyświetlanie zamglenia. Aby okno było półprzezroczyste, w motywie aktywności ustawiamy wartość R.attr.windowIsTranslucent na true:

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

Producenci OEM i partnerzy

Aby umożliwić rozmycie okna na urządzeniu, producent OEM musi zadeklarować, że urządzenie obsługuje rozmycie okna.

Aby sprawdzić, czy Twoje urządzenie obsługuje rozmycie okna, wykonaj te czynności:

  • Upewnij się, że urządzenie może obsłużyć dodatkowe obciążenie karty graficznej. Urządzenia tańsze mogą nie być w stanie obsłużyć dodatkowego obciążenia, co może spowodować utratę klatek. Umożliwiaj rozmycie okna tylko na testowanych urządzeniach z wystarczającą mocą GPU.

  • Jeśli masz niestandardowy silnik renderowania, upewnij się, że implementuje on logikę rozmywania. Domyślny silnik renderowania w Androidzie 12 stosuje logikę rozmywania w BlurFilter.cpp.

Gdy upewnisz się, że Twoje urządzenie obsługuje rozmycie okna, skonfiguruj te ustawienia sysprop:

PRODUCT_VENDOR_PROPERTIES += \
       ro.surface_flinger.supports_background_blur=1

Weryfikacja

Aby sprawdzić, czy okno aplikacji jest prawidłowo obsługiwane podczas przełączania się między stanami włączonego i wyłączonego rozmycia, wykonaj te czynności:

  1. Otwórz interfejs z rozmyciem.

  2. Włącz lub wyłącz rozmycie okna, włączając i wyłączając tę funkcję.

  3. Sprawdź, czy interfejs okna zmienia się zgodnie z oczekiwaniami z przezroczystego w nieprzezroczysty i odwrotnie.

Włączanie i wyłączanie rozmywania okna

Aby przetestować, jak interfejs okna jest renderowany z efektem rozmycia okna, włącz lub wyłącz rozmycia, korzystając z jednej z tych metod:

  • W Opcjach programisty:

    Ustawienia > System > Opcje programisty > Renderowanie przyspieszone sprzętowo > Zezwalaj na rozmycie na poziomie okna.

  • W terminalu na urządzeniu z dostępem do roota:

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

Aby sprawdzić, czy urządzenie z Androidem w wersji 12 lub nowszej obsługuje rozmycie okna i czy jest ono obecnie włączone, uruchom adb shell wm disable-blur na urządzeniu z rootem.

Rozwiązywanie problemów

Poniżej znajdziesz wskazówki dotyczące rozwiązywania problemów podczas weryfikacji.

Nie ma zastosowanego rozmycia

  • Sprawdź, czy rozmycie jest obecnie włączone i czy Twój sprzęt je obsługuje. Zapoznaj się z artykułem Włączanie i wyłączanie rozmywania okna.

  • Upewnij się, że ustawiony jest przezroczysty kolor tła okna. Nieprzezroczysty kolor tła okna ukrywa rozmyty obszar.

Urządzenie testowe nie obsługuje rozmyć okna

  • przetestować aplikację na emulatorze Androida 12. Aby skonfigurować emulator Androida, przeczytaj artykuł Konfigurowanie emulatora Androida. Każde urządzenie wirtualne z Androidem utworzone za pomocą emulatora obsługuje rozmycie okna.

Brak zaokrąglonych narożników

Aktualizacja opcji programisty nie włącza rozmyć

  • Sprawdź, czy urządzenie jest w trybie oszczędzania baterii lub czy korzysta z tunelowania multimediów. Na niektórych telewizorach rozmycie okna może być wyłączone podczas odtwarzania filmu.

Rozmycie tła wyświetlane na pełnym ekranie, a nie w obrębie okna

Zmiany z detektora nie są stosowane na ekranie

  • Aktualizacje dotyczące słuchacza mogą być stosowane w starej instancji okna. Sprawdź, czy okno jest niszczone i powtarzane z odpowiednią aktualizacją listenera.