Desfoques de janela

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

Os desfoque de janela ou de várias janelas são usados para desfocar a tela atrás da janela. 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.

  • Desfocar por trás permite desfocar toda a tela por trá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 abaixo:

apenas desfoque do plano de fundo

a

desfocar somente o plano de fundo

b

desfoque do plano de fundo e desfoque do plano de fundo

c

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

O recurso de desfoque de janela funciona em todas as janelas, o que significa que ele também funciona quando há outro app por trás da janela. Esse efeito não é o mesmo que o efeito de renderização desfocado, 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, ou seja, quanto maior o raio, mais densa é a desfocagem. Um desfoque de 0 px significa que não há desfoque. Para desfoque por trás, um raio de 20 px cria um bom efeito de profundidade de campo, enquanto um raio de desfoque 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 de segundo plano em janelas flutuantes para criar um efeito de segundo plano de janela, que é uma imagem desfocada do conteúdo. 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. Portanto, ela 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 para a área desfocada definindo um ShapeDrawable com cantos arredondados como o drawable de plano de fundo da janela.

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

Desfocar o plano de fundo

O desfoque em segundo plano 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 desfoque atrás do raio. Ou, no tema da janela, defina R.attr.windowBlurBehindRadius.

  3. Opcionalmente, escolha um valor de sombra complementar.

  4. Processar 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 para 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 a versão do Android no SDK do dispositivo.

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

  • Estado do sistema: o servidor do sistema pode desativar temporariamente o desfoque de janelas no tempo de execução, por exemplo, durante o modo de economia de bateria, durante a reprodução de determinados tipos de conteúdo de vídeo ou devido a uma substituição do desenvolvedor.

Para tornar seu app 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 o desfoque de janela estiver ativado ou desativado. Além disso, use WindowManager#isCrossWindowBlurEnabled para consultar se o desfoque de janela está ativado.

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

    Quando o desfoque é ativado, 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 principal, tornando a janela sobreposta menos legível. Para evitar esse efeito, quando o desfoque de janela for desativado, adapte a interface do app da seguinte maneira:

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

    • Para desfocar o plano de fundo, adicione uma camada escura com um valor mais alto.

Exemplo de desfoque atrás e desfoque de segundo plano

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

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 segundo plano 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 desfoque de janela) e mudanças de desfoque de execução ativadas ou desativadas. Ele garante que o conteúdo da caixa de diálogo seja legível em qualquer uma dessas condições ajustando o alfa de drawable de segundo plano 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>

O desfoque da janela desfoca o conteúdo da janela abaixo da atividade. A imagem desfocada é exibida abaixo desta janela de atividade. Portanto, a janela de atividade precisa ser translúcida para que a desfoque fique 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 o dispositivo oferece suporte a desfoque de janela.

Para verificar se o dispositivo oferece suporte a desfoque 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 processar a carga extra, o que pode causar a perda de frames. Só ative o desfoque de janela 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 motor de renderização padrão do Android 12 implementa a lógica de desfoque em BlurFilter.cpp.

Depois de garantir que o dispositivo pode oferecer suporte a desfoque de janela, defina o sysprop do Surface flinger:

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 e de um estado desfocado conforme o esperado.

Ativar e desativar o desfoque da janela

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

  • Nas Opções do desenvolvedor:

    Configurações -> Sistema -> Opções do desenvolvedor -> Renderização com aceleração de hardware -> Permitir desfoque 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 conferir se o dispositivo Android 12 ou mais recente oferece suporte a 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 Blurs estão ativados e se o hardware é compatível com eles. Consulte Ativar e desativar o desfoque da janela.

  • Defina uma cor de plano de fundo translúcido para a janela. Uma cor de plano de fundo opaco oculta a área desfocada.

O dispositivo de teste não oferece suporte a desfoque 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 oferece suporte ao desfoque de janela.

Sem cantos arredondados

Atualizar a opção de desenvolvedor não ativa o desfoque.

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

O desfoque do plano de fundo é exibido 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.