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 o plano de fundo 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:
a |
b |
C |
Figura 1. Desfoque do plano de fundo (a), desfoque do plano de fundo e do plano de fundo (b), desfoque do plano de fundo e do plano de fundo (c)
O recurso de desfoque de janela funciona em diferentes janelas, o que significa que também funciona quando há outro app atrá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. 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. Quanto maior o raio, maior a densidade. 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 acima de 150 px, já que 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 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:
Chame Window#setBackgroundBlurRadius(int) para definir um raio de desfoque do plano de fundo. Ou, no tema da janela, defina R.attr.windowBackgroundBlurRadius.
Defina R.attr.windowIsTranslucent como "true" para deixar 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.
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.
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.
Processar estados de desfoque ativado e desativado. Consulte a seção Diretrizes para usar o desfoque de janela em apps para mais informações.
Desfoque atrás
O desfoque atrás da janela desfoca toda a tela. 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:
Adicione
FLAG_BLUR_BEHIND
às flags da janela para ativar o desfoque em segundo plano. Ou, no tema da janela, defina R.attr.windowBlurBehindEnabled.Chame
WindowManager.LayoutParams#setBlurBehindRadius
para definir um desfoque atrás do raio. Ou, no tema da janela, defina R.attr.windowBlurBehindRadius.Opcionalmente, escolha um valor de sombra complementar.
Lidar com os estados ativado e desativado de 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 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 as versões do Android, os dispositivos e os estados do sistema, siga estas diretrizes:
Adicione um listener usando WindowManager#addCrossWindowBlurEnabledListener, para receber notificações quando os desfoques de janela forem ativados ou desativados. 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 do desfoque da 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 subjacente, 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 do plano de fundo da janela, tornando-o mais opaco.
Para desfocar atrás, adicione uma camada escura com uma quantidade de escurecimento mais alta.
Exemplo de desfoque atrás e no plano de fundo
Esta seção fornece um exemplo funcional de uma atividade que usa desfoque por trás e desfoque de segundo plano.
O exemplo abaixo de MainActivity.java
é uma caixa de diálogo com um desfoque
atrás de um raio de 20 px e um raio de desfoque do plano de fundo de 80 px. Ela tem
cantos arredondados, definidos em xml no drawable de segundo plano 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 drawable Alfa do 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 desta janela de atividade. Portanto, a janela de atividade precisa ser translúcida para que a desfocagem 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 usar o desfoque de janela em um dispositivo, o OEM precisa declarar que o dispositivo oferece suporte ao desfoque de janelas.
Para verificar se seu dispositivo é compatível com desfoques de janela, faça o seguinte:
Verifique se o dispositivo consegue processar a carga extra da GPU. Dispositivos mais simples podem não conseguir processar a carga extra, o que pode causar a queda de frames. Só ative desfoques 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 mecanismo 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:
Abra a interface que tem desfoque.
Ative ou desative o desfoque de janela ativando ou desativando o recurso.
Verifique se a interface da janela muda para e de um estado desfocado conforme o esperado.
Ativar e desativar o desfoque de 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 verificar 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 solucionar 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 de janela.
Defina uma cor de plano de fundo para a janela translúcida. Uma cor de fundo da janela opaca 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 é compatível com o desfoque de janelas.
Sem cantos arredondados
- Defina um drawable de plano de fundo da janela para definir cantos arredondados. Esse drawable determina o contorno da área desfocada.
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 encaminhamento de mídia. Em alguns dispositivos de TV, o desfoque de janela também pode ser desativado durante a reprodução do vídeo.
Desfoque do plano de fundo mostrado em tela cheia, fora dos limites da janela
Verifique android:windowIsFloating para garantir que a janela esteja marcada como flutuante.
Confira se um drawable de plano de fundo da janela está definido. Essa configuração determina o desenho da área desfocada.
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 da janela antiga. Verifique se a janela está sendo destruída e recriada com a atualização correta do listener.