Desfoques de janela

No Android 12, as APIs públicas estão disponíveis para implementar efeitos de desfoque de janela, como no plano de fundo e segundo plano.

Os desfoques de janela, ou desfoques entre janelas, são usados para desfocar a tela atrás da janela em questão. Há dois tipos de desfoque de janela, que podem ser usados para alcançar diferentes efeitos visuais:

  • O desfoque do plano de fundo permite criar janelas com planos de fundo desfocados, criando um efeito de vidro fosco.

  • O desfoque de fundo permite desfocar toda a tela atrás de uma janela (caixa de diálogo), criando um efeito de profundidade de campo.

Os dois efeitos podem ser usados separadamente ou combinados, como mostrado na figura a seguir:

somente desfoque do plano de fundo

a

desfocar somente atrás

b

desfoque atrás e desfoque do plano de fundo

c

Figura 1. Somente desfoque do plano de fundo (a), somente desfoque atrás (b), desfoque do plano de fundo e desfoque atrás (c)

O recurso de desfoque de janela funciona em várias janelas, o que significa que ele também funciona quando há outro app atrás da janela. Esse efeito não é o mesmo que o efeito de renderização de desfoque, que desfoca o conteúdo dentro da mesma janela. Os desfoques de janela são úteis para caixas de diálogo, páginas inferiores e outras janelas flutuantes.

Implementação

Desenvolvedores de apps

Os desenvolvedores de apps precisam fornecer um raio de desfoque para criar um efeito de desfoque. O raio de desfoque controla a densidade do desfoque, ou seja, quanto maior o raio, mais denso o desfoque. Um desfoque de 0 px significa que não há desfoque. Para o desfoque atrás, um raio de 20 px cria um bom efeito de profundidade de campo, enquanto um raio de desfoque de fundo de 80 px cria um bom efeito de vidro fosco. Evite raios de desfoque maiores que 150 px, porque isso afeta significativamente o desempenho.

Para alcançar o efeito de desfoque desejado e aumentar a legibilidade, escolha um valor de raio de desfoque complementado com uma camada de cor translúcida.

Desfoque do plano de fundo

Use o desfoque de plano de fundo em janelas flutuantes para criar um efeito de plano de fundo de janela, que é uma imagem desfocada do conteúdo subjacente. Para adicionar um plano de fundo desfocado à janela, faça o seguinte:

  1. Chame Window#setBackgroundBlurRadius(int) para definir um raio de desfoque do plano de fundo. Ou, no tema da janela, defina R.attr.windowBackgroundBlurRadius.

  2. Defina R.attr.windowIsTranslucent como verdadeiro para tornar a janela translúcida. O desfoque é desenhado sob a superfície da janela, então ela precisa ser translúcida para que o desfoque fique visível.

  3. Se quiser, chame Window#setBackgroundDrawableResource(int) para adicionar um drawable de plano de fundo de janela retangular com uma cor translúcida. Ou, no tema da janela, defina R.attr.windowBackground.

  4. Para uma janela com cantos arredondados, determine os cantos arredondados da área desfocada definindo um ShapeDrawable com cantos arredondados como o drawable de plano de fundo da janela.

  5. Gerenciar os estados ativado e desativado do desfoque. Consulte a seção Diretrizes para usar o desfoque de janela em apps para mais informações.

Desfoque atrás

O desfoque de fundo desfoca toda a tela atrás da janela. Esse efeito é usado para direcionar a atenção do usuário para o conteúdo da janela, desfocando tudo na tela atrás dela.

Para desfocar o conteúdo atrás da janela, siga estas etapas:

  1. Adicione FLAG_BLUR_BEHIND às flags da janela para ativar o desfoque de fundo. Ou, no tema da janela, defina R.attr.windowBlurBehindEnabled.

  2. Chame WindowManager.LayoutParams#setBlurBehindRadius para definir um raio de desfoque de fundo. Ou, no tema da janela, defina R.attr.windowBlurBehindRadius.

  3. Se quiser, escolha uma quantidade de escurecimento complementar.

  4. Gerenciar os estados ativado e desativado do desfoque. Consulte a seção Diretrizes para usar o desfoque de janela em apps para mais informações.

Diretrizes para usar o desfoque de janela em apps

O suporte ao desfoque de janelas depende do seguinte:

  • Versão do Android: as APIs de desfoque de janelas estão disponíveis apenas no Android 12 e versões mais recentes. Verifique o SDK do dispositivo para saber a versão do Android.

  • Desempenho gráfico: dispositivos com GPUs de menor desempenho podem optar por não oferecer suporte a desfoques de janela.

  • Estado do sistema: o servidor do sistema pode desativar temporariamente os desfoques de janela no momento da execução, por exemplo, durante o modo de economia de bateria, ao reproduzir determinados tipos de conteúdo de vídeo ou devido a uma substituição do desenvolvedor.

Para que seu app seja compatível com todas as versões do Android, dispositivos e estados do sistema, siga estas diretrizes:

  • Adicione um listener usando WindowManager#addCrossWindowBlurEnabledListener para receber uma notificação quando os desfoques de janela forem ativados ou desativados. Além disso, use WindowManager#isCrossWindowBlurEnabled para consultar se os desfoques de janela estão ativados.

  • Implemente duas versões para o plano de fundo da janela para acomodar o estado ativado ou desativado dos desfoques de janela.

    Quando os desfoques estão ativados, o plano de fundo da janela precisa ser translúcido para que o desfoque fique visível. Nesse estado, quando os desfoques são desativados, o conteúdo da janela se sobrepõe diretamente ao conteúdo da janela de baixo, tornando a janela sobreposta menos legível. Para evitar esse efeito, quando os desfoques de janela estiverem desativados, adapte a IU do app da seguinte maneira:

    • Para o desfoque do plano de fundo, aumente o alfa do drawable do plano de fundo da janela, tornando-o mais opaco.

    • Para desfocar o fundo, adicione uma camada de escurecimento com um valor maior.

Exemplo de desfoque atrás e desfoque de fundo

Esta seção mostra um exemplo de atividade que usa o desfoque atrás e o desfoque de fundo.

O exemplo a seguir de MainActivity.java é uma caixa de diálogo com um raio de desfoque atrás de 20 px e um raio de desfoque de plano de fundo de 80 px. Ele tem cantos arredondados, definidos em XML no drawable de plano de fundo da janela. Ele processa corretamente diferentes versões do Android, diferentes dispositivos (que podem não oferecer suporte ao desfoque de janela) e mudanças de ativação ou desativação do desfoque de execução. Ele garante que o conteúdo da caixa de diálogo seja legível em qualquer uma dessas condições, ajustando o alfa drawable do plano de fundo da janela e a quantidade de escurecimento da janela.

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

Para criar cantos arredondados para a janela, definimos o plano de fundo dela em res/drawable/window_background.xml como um ShapeDrawable com cantos arredondados com raio de 20 dp, da seguinte maneira:

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

O desfoque de janela desfoca o conteúdo da janela abaixo da atividade. A imagem desfocada é desenhada abaixo dessa janela de atividade. Portanto, a janela de atividade precisa ser translúcida para que o desfoque seja visível. Para tornar a janela translúcida, defina R.attr.windowIsTranslucent no tema da atividade da seguinte maneira:

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

OEMs e parceiros

Para que um dispositivo tenha desfoque de janela, o OEM precisa declarar que o dispositivo oferece suporte a esse recurso.

Para verificar se o dispositivo é compatível com desfoques de janela, faça o seguinte:

  • Verifique se o dispositivo pode lidar com a carga extra da GPU. Dispositivos mais simples podem não conseguir lidar com a carga extra, o que pode causar frames perdidos. Ative o desfoque de janela apenas em dispositivos testados com capacidade de GPU suficiente.

  • Se você tiver um mecanismo de renderização personalizado, verifique se ele implementa a lógica de desfoque. O mecanismo de renderização padrão do Android 12 implementa a lógica de desfoque em BlurFilter.cpp.

Depois de garantir que o dispositivo é compatível com desfoques de janela, defina o seguinte surface flinger sysprop:

PRODUCT_VENDOR_PROPERTIES += \
       ro.surface_flinger.supports_background_blur=1

Validação

Para validar se a janela do app tem o processamento adequado ao alternar entre os estados de desfoque ativado e desativado, siga estas etapas:

  1. Abra a interface que tem desfoque.

  2. Ative ou desative o desfoque de janela ativando e desativando o desfoque de janela.

  3. Verifique se a interface da janela muda para um estado desfocado e volta ao normal conforme o esperado.

Ativar e desativar o desfoque de janela

Para testar como a interface da janela é renderizada com o efeito de desfoque, ative ou desative o desfoque usando um dos seguintes métodos:

  • Em Opções do desenvolvedor:

    Configurações -> Sistema -> Opções do desenvolvedor -> Aceleração de hardware -> Permitir desfoques no nível da janela

  • No terminal de um dispositivo com acesso root:

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

Para verificar se o dispositivo Android 12+ oferece suporte ao desfoque de janela e se ele está ativado, execute adb shell wm disable-blur em um dispositivo com acesso root.

Solução de problemas

Use as informações a seguir como um guia para resolver problemas durante a validação.

Nenhum desfoque desenhado

  • Verifique se os desfoques estão ativados e se o hardware é compatível com eles. Consulte Ativar e desativar o desfoque de janela.

  • Defina uma cor translúcida para o plano de fundo da janela. Uma cor de plano de fundo opaca oculta a área desfocada.

O dispositivo de teste não é compatível com desfoques de janela

  • Teste o aplicativo no emulador do Android 12. Para configurar um Android Emulator, consulte Configurar um Android Emulator. Qualquer dispositivo virtual Android criado com o emulador é compatível com o desfoque de janela.

Sem cantos arredondados

Atualizar a opção para desenvolvedores não ativa os desfoques

  • Verifique se o dispositivo está no modo de economia de bateria ou se está usando o tunelamento de multimídia. Em alguns dispositivos de TV, o desfoque de janela também pode ser desativado durante a reprodução de vídeos.

O desfoque do plano de fundo é desenhado em tela cheia, não dentro dos limites da janela

As atualizações do listener não são aplicadas na tela

  • As atualizações do listener podem estar sendo aplicadas a uma instância de janela antiga. Verifique se a janela está sendo destruída e recriada com a atualização correta do listener.