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 especificada. Há dois tipos de desfoques 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 do segundo plano permite desfocar toda a tela atrás de uma janela (de diálogo), criando um efeito de profundidade de campo.

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

desfoque do plano de fundo apenas

a

desfocar apenas o que está atrás

b

desfoque atrás e desfoque do plano de fundo

c

Figura 1. Desfoque do plano de fundo (a), desfoque do segundo plano (b), desfoque do plano de fundo e do segundo plano (c)

O recurso de desfoque de janela funciona em todas as janelas, o que significa que ele também funciona quando há outro app atrás da sua 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, folhas de baixo 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 do segundo plano, um raio de 20 px cria um bom efeito de profundidade de campo, enquanto um raio de desfoque do plano de fundo de 80 px cria um bom efeito de vidro fosco. Evite raios de desfoque maiores que 150 px, porque isso afeta significativamente a performance.

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

Desfoque do plano de fundo

Use o desfoque do 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 abaixo da superfície da janela, então a janela precisa ser translúcida para que o desfoque fique visível.

  3. Opcionalmente, 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. Gerencie os estados de desfoque ativado e desativado. Consulte a seção Diretrizes para usar o desfoque de janela em apps para mais informações.

Desfoque do segundo plano

O desfoque do segundo plano 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 da janela.

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 do segundo plano. Ou, no tema da janela, defina R.attr.windowBlurBehindEnabled.

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

  3. Opcionalmente, escolha um valor de escurecimento complementar .

  4. Gerencie os estados de desfoque ativado e desativado. 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 a versão do Android.

  • Performance gráfica: dispositivos com GPUs de menor performance 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 tornar seu app compatível com versões, dispositivos e estados do sistema do Android, 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 no momento.

  • 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 tornar o desfoque visível. Nesse estado, quando os desfoques são desativados, o conteúdo da janela se sobrepõe diretamente ao conteúdo da janela subjacente, tornando a janela sobreposta menos legível. Para evitar esse efeito, quando os desfoques de janela estão desativados, adapte a interface 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 o desfoque do segundo plano, adicione uma camada de escurecimento com um valor de escurecimento maior.

Exemplo de desfoque do segundo plano e do plano de fundo

Esta seção fornece um exemplo funcional de uma atividade que usa o desfoque do segundo plano e do plano de fundo.

O exemplo a seguir de MainActivity.java é uma caixa de diálogo com um raio de desfoque do segundo plano de 20 px e um raio de desfoque do 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 a desfoques de janela) e mudanças ativadas ou desativadas de desfoque no momento da 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 do drawable de plano de fundo da janela e o valor 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 da janela 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>

Os desfoques de janela desfocam o conteúdo da janela abaixo da atividade. A imagem desfocada é desenhada abaixo dessa janela de atividade, então a janela de atividade precisa ser translúcida para que o desfoque fique visível. Para tornar a janela translúcida, definimos R.attr.windowIsTranslucent em o 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 ter o desfoque de janela em um dispositivo, o OEM precisa declarar que o dispositivo oferece suporte a desfoques de janela.

Para verificar se o dispositivo oferece suporte a desfoques de janela, faça o seguinte:

  • Verifique se o dispositivo consegue processar a carga extra da GPU. Dispositivos de baixa qualidade podem não conseguir processar a carga extra, o que pode causar frames descartados. Ative os desfoques de janela apenas em dispositivos testados com potência 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 verificar se o dispositivo oferece suporte a desfoques de janela, defina o seguinte sysprop do SurfaceFlinger:

PRODUCT_VENDOR_PROPERTIES += \
       ro.surface_flinger.supports_background_blur=1

Validação

Para validar se a janela do app tem o tratamento 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 os desfoques de janela ativando e desativando o desfoque de janela.

  3. Verifique se a interface da janela muda para um estado desfocado e vice-versa conforme o esperado.

Ativar e desativar o desfoque de janela

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

  • Nas opções do desenvolvedor:

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

  • No terminal em 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 ou mais recente oferece suporte a desfoques de janela e se eles estão ativados no momento, execute adb shell wm disable-blur em um dispositivo com acesso root.

Solução de problemas

Use o seguinte como um guia para solucionar problemas durante a validação.

Nenhum desfoque desenhado

  • Verifique se os desfoques estão ativados e se o hardware oferece suporte a eles. Consulte Ativar e desativar o desfoque de janela.

  • Verifique se você definiu uma cor de plano de fundo de janela translúcida. Uma cor de plano de fundo de janela opaca oculta a área desfocada.

O dispositivo de teste não oferece suporte a desfoques de janela

  • Teste seu aplicativo no emulador do Android 12. Para configurar um emulador do Android, consulte Configurar um emulador do Android. Qualquer dispositivo virtual do Android criado com o emulador oferece suporte aos desfoques de janela.

Sem cantos arredondados

A atualização da opção do desenvolvedor não ativa os desfoques

  • Verifique se o dispositivo está no modo de economia de bateria ou se está usando túnel multimídia. Em alguns dispositivos de TV, os desfoques de janela também podem ser desativados durante a reprodução de vídeo.

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 do listener correta.