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.

O desfoque de janela ou entre janelas é usado para desfocar a tela atrás da janela especificada. Há dois tipos de desfoque de janela, que podem ser usados para alcançar diferentes efeitos visuais:

  • Com o desfoque do plano de fundo, é possível criar janelas com planos de fundo desfocados, criando um efeito de vidro fosco.

  • Com a opção Desfocar atrás, é possível 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 atrás

b

desfoque atrás e desfoque do plano de fundo

c

Figura 1. Desfoque do plano de fundo apenas (a), desfoque atrás apenas (b), desfoque do plano de fundo e desfoque atrás (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. O desfoque de janela é útil 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. Quanto maior o raio, mais denso o desfoque. Um desfoque de 0 px significa que não há desfoque. Para o desfoque em segundo plano, um raio de 20 px cria um bom efeito de profundidade de campo, enquanto um raio de desfoque de plano de fundo de 80 px cria um bom efeito de vidro fosco. Evite raios de desfoque maiores que 150 px, porque isso afeta muito a performance.

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

Desfoque do 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 "true" para tornar a janela translúcida. O desfoque é desenhado abaixo da 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 plano de fundo retangular com uma cor translúcida. Ou, no tema da janela, defina R.attr.windowBackground.

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

  5. Processar estados de desfoque ativados e desativados. Consulte a seção Diretrizes para usar o desfoque de janela em apps para mais informações.

Desfoque atrás

O desfoque atrás desfoca toda a tela atrás da janela. Esse efeito é usado para direcionar a atenção do usuário ao 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 em segundo plano. Ou, no tema da janela, defina R.attr.windowBlurBehindEnabled.

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

  3. Se quiser, escolha um valor de redução complementar.

  4. Processar estados de desfoque ativados e desativados. 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 janela estão disponíveis apenas no Android 12 e em versões mais recentes. Verifique o SDK do dispositivo para a versão do Android.

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

  • Estado do sistema: o servidor do sistema pode desativar temporariamente os desfoques de janela em tempo de 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 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.

  • 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 subjacente, tornando a janela sobreposta menos legível. Para evitar esse efeito, quando os desfoques de janela estiverem desativados, adapte a interface do app da seguinte maneira:

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

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

Exemplo de desfoque atrás e desfoque de segundo plano

Esta seção fornece um exemplo funcional de uma atividade que usa desfoque atrás e desfoque de plano de fundo.

O exemplo a seguir de MainActivity.java é uma caixa de diálogo com um raio de desfoque de 20 px e um raio de desfoque de plano de fundo de 80 px. Ele tem bordas arredondadas, definidas 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 de ativação ou desativação do desfoque em tempo de execução. Isso 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 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 da janela de atividade. Portanto, a janela de atividade precisa ser translúcida para permitir que o desfoque seja visível. Para tornar a janela translúcida, definimos 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 ter o desfoque de janela em um dispositivo, o OEM precisa declarar que ele é compatível com esse recurso.

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

  • Verifique se o dispositivo consegue lidar com a carga extra da GPU. Dispositivos mais simples podem não conseguir lidar com a carga extra, o que pode causar perda de frames. Ative o desfoque de janelas 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 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 é processada corretamente ao alternar entre os estados de desfoque ativado e desativado, siga estas etapas:

  1. Abra a interface com desfoque.

  2. Para ativar ou desativar o desfoque da janela, ative e desative essa opção.

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

Ativar e desativar o desfoque da janela

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

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

Solução de problemas

Use o seguinte 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 da janela.

  • Defina uma cor translúcida para o plano de fundo da janela. Uma cor de segundo plano opaca da janela 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 do desenvolvedor não ativa os desfoques

  • Verifique se o dispositivo está no modo de economia de bateria ou se está usando tunelamento multimídia. Em alguns dispositivos de TV, o desfoque da janela também pode ser desativado 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.