Desenfoque de ventanas

En Android 12, las APIs públicas están disponibles para implementar efectos de desenfoque de ventana, como desenfoque del fondo y desenfoque detrás.

Los desenfoques de ventana o de ventana cruzada se usan para desenfocar la pantalla detrás de una ventana determinada. Hay dos tipos de desenfoques de ventana que se pueden usar para lograr diferentes efectos visuales:

  • El desenfoque de fondo te permite crear ventanas con fondos desenfocados, lo que genera un efecto de cristal esmerilado.

  • Desenfocar detrás te permite desenfocar toda la pantalla detrás de una ventana (de diálogo), lo que genera un efecto de profundidad de campo.

Los dos efectos se pueden usar por separado o combinados, como se muestra en la siguiente imagen:

solo desenfoque de fondo

a

desenfoque solo de la parte posterior

b

desenfoque detrás y desenfoque del fondo

c

Figura 1: Solo desenfoque del fondo (a), solo desenfoque detrás (b), desenfoque del fondo y desenfoque detrás (c)

La función de desenfoque de la ventana funciona en todas las ventanas, lo que significa que también funciona cuando hay otra app detrás de la ventana. Este efecto no es el mismo que el efecto de renderización de desenfoque, que desenfoca el contenido dentro de la misma ventana. Los desenfoques de ventana son útiles para los diálogos, las hojas inferiores y otras ventanas flotantes.

Implementación

Desarrolladores de apps

Los desarrolladores de apps deben proporcionar un radio de desenfoque para crear un efecto de desenfoque. El radio de desenfoque controla la densidad del desenfoque, es decir, cuanto mayor sea el radio, más denso será el desenfoque. Un desenfoque de 0 px significa que no hay desenfoque. Para el desenfoque detrás, un radio de 20 px crea un buen efecto de profundidad de campo, mientras que un radio de desenfoque de fondo de 80 px crea un buen efecto de cristal esmerilado. Evita los radios de desenfoque superiores a 150 px, ya que esto afectará significativamente el rendimiento.

Para lograr el efecto de desenfoque deseado y aumentar la legibilidad, elige un valor de radio de desenfoque complementado con una capa translúcida de color.

Desenfoque del fondo

Usa el desenfoque de fondo en las ventanas flotantes para crear un efecto de fondo de ventana que es una imagen desenfocada del contenido subyacente. Para agregar un fondo desenfocado a la ventana, haz lo siguiente:

  1. Llama a Window#setBackgroundBlurRadius(int) para establecer un radio de desenfoque del fondo. O bien, en el tema de la ventana, establece R.attr.windowBackgroundBlurRadius.

  2. Establece R.attr.windowIsTranslucent como verdadero para que la ventana sea translúcida. El desenfoque se dibuja debajo de la superficie de la ventana, por lo que esta debe ser translúcida para que se pueda ver el desenfoque.

  3. De manera opcional, llama a Window#setBackgroundDrawableResource(int) para agregar un elemento de diseño de fondo de ventana rectangular con un color translúcido. O bien, en el tema de la ventana, establece R.attr.windowBackground.

  4. Para una ventana con esquinas redondeadas, determina las esquinas redondeadas del área desenfocada configurando un ShapeDrawable con esquinas redondeadas como el elemento de diseño de fondo de la ventana.

  5. Controla los estados de desenfoque habilitados y no habilitados. Para obtener más información, consulta la sección Lineamientos para usar el desenfoque de ventanas en apps.

Desenfocar detrás

El desenfoque detrás desenfoca toda la pantalla detrás de la ventana. Este efecto se usa para dirigir la atención del usuario hacia el contenido de la ventana, ya que desenfoca todo lo que se encuentra en la pantalla detrás de ella.

Para desenfocar el contenido detrás de la ventana, sigue estos pasos:

  1. Agrega FLAG_BLUR_BEHIND a las marcas de ventana para habilitar el desenfoque del fondo. O bien, en el tema de la ventana, establece R.attr.windowBlurBehindEnabled.

  2. Llama a WindowManager.LayoutParams#setBlurBehindRadius para establecer un desenfoque detrás del radio. O bien, en el tema de la ventana, establece R.attr.windowBlurBehindRadius.

  3. De manera opcional, elige un importe de dimensión complementario.

  4. Controla los estados de desenfoque habilitados y no habilitados. Para obtener más información, consulta la sección Lineamientos para usar el desenfoque de ventanas en apps.

Lineamientos para usar el desenfoque de ventanas en apps

La compatibilidad con la función de desenfoque de ventanas depende de lo siguiente:

  • Versión de Android: Las APIs de desenfoque de ventanas solo están disponibles en Android 12 y versiones posteriores. Verifica la versión de Android en el SDK del dispositivo.

  • Rendimiento de los gráficos: Es posible que los dispositivos con GPUs de menor rendimiento no admitan desenfoques de ventana.

  • Estado del sistema: Es posible que el servidor del sistema inhabilite temporalmente los desenfoques de ventana durante el tiempo de ejecución, por ejemplo, durante el modo de ahorro de batería, mientras se reproducen ciertos tipos de contenido de video o debido a una anulación del desarrollador.

Para que tu app sea compatible con todas las versiones de Android, dispositivos y estados del sistema, sigue estos lineamientos:

  • Agrega un objeto de escucha a través de WindowManager#addCrossWindowBlurEnabledListener para que te notifique cuando se habiliten o inhabiliten los desenfoques de la ventana. Además, usa WindowManager#isCrossWindowBlurEnabled para consultar si los desenfoques de ventana están habilitados actualmente.

  • Implementa dos versiones para el fondo de la ventana, de modo que se adapte al estado habilitado o inhabilitado de los desenfoques de la ventana.

    Cuando se habilitan los desenfoques, el fondo de la ventana debe ser translúcido para que se vea el desenfoque. En este estado, cuando se inhabilitan los desenfoques, el contenido de la ventana se superpone directamente con el contenido de la ventana subyacente, lo que hace que la ventana superpuesta sea menos legible. Para evitar este efecto, cuando se inhabiliten los desenfoques de la ventana, adapta la IU de la app de la siguiente manera:

    • Para el desenfoque de fondo, aumenta el valor alfa del elemento de diseño de fondo de la ventana para que sea más opaco.

    • Para desenfocar el fondo, agrega una capa atenuada con un valor de atenuación más alto.

Ejemplo de desenfoque detrás y desenfoque de fondo

En esta sección, se proporciona un ejemplo funcional de una actividad que usa desenfoque detrás y desenfoque de fondo.

El siguiente ejemplo de MainActivity.java es un diálogo con un radio de desenfoque detrás de 20 px y un radio de desenfoque de fondo de 80 px. Tiene esquinas redondeadas, definidas en XML en el elemento de diseño de fondo de la ventana. Controla correctamente las diferentes versiones de Android, los diferentes dispositivos (que potencialmente no admiten desenfoques de ventana) y los cambios de desenfoque del entorno de ejecución habilitados o inhabilitados. Garantiza que el contenido del diálogo sea legible en cualquiera de esas condiciones ajustando la alfa del elemento de diseño del fondo de la ventana y la cantidad de atenuación de la ventana.

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 crear esquinas redondeadas para la ventana, definimos el fondo de la ventana en res/drawable/window_background.xml como un ShapeDrawable con esquinas redondeadas con un radio de 20 dp de la siguiente manera:

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

Los desenfoques de ventana desenfocan el contenido de la ventana debajo de la actividad. La imagen difuminada se dibuja debajo de esta ventana de actividad, por lo que la ventana de actividad debe ser translúcida para permitir que se vea el desenfoque. Para que la ventana sea translúcida, configuramos R.attr.windowIsTranslucent en el tema de la actividad de la siguiente manera:

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

OEMs y socios

Para tener desenfoque de ventana en un dispositivo, el OEM debe declarar que el dispositivo admite desenfoques de ventana.

Para verificar si tu dispositivo puede admitir desenfoques de ventana, haz lo siguiente:

  • Asegúrate de que el dispositivo pueda controlar la carga adicional de la GPU. Es posible que los dispositivos de gama baja no puedan controlar la carga adicional, lo que puede provocar la pérdida de fotogramas. Habilita solo los desenfoques de ventana en los dispositivos probados con suficiente potencia de GPU.

  • Si tienes un motor de renderización personalizado, asegúrate de que implemente la lógica de desenfoque. El motor de renderización predeterminado de Android 12 implementa la lógica de desenfoque en BlurFilter.cpp.

Una vez que te asegures de que tu dispositivo pueda admitir desenfoques de ventana, establece el siguiente sysprop de lanzador de superficie:

PRODUCT_VENDOR_PROPERTIES += \
       ro.surface_flinger.supports_background_blur=1

Validación

Para validar que la ventana de tu app tenga un control adecuado cuando cambies entre los estados de desenfoque habilitado y desenfoque inhabilitado, sigue estos pasos:

  1. Abre la IU que tiene desenfoque.

  2. Activa o desactiva el desenfoque de la ventana para habilitarlo o inhabilitarlo.

  3. Verifica que la IU de la ventana cambie de un estado difuminado a uno normal y viceversa como se espera.

Activa y desactiva el desenfoque de la ventana

Para probar cómo se renderiza la IU de la ventana con el efecto de desenfoque de la ventana, habilita o inhabilita los desenfoques con uno de los siguientes métodos:

  • En Opciones para desarrolladores, haz lo siguiente:

    Configuración -> Sistema -> Opciones para desarrolladores -> Renderización con aceleración de hardware -> Permitir desenfoques a nivel de la ventana

  • Desde la terminal de un dispositivo con permisos de administrador, haz lo siguiente:

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

Para verificar si tu dispositivo con Android 12 o versiones posteriores admite desenfoques de ventana y si estos están habilitados actualmente, ejecuta adb shell wm disable-blur en un dispositivo con permisos de administrador.

Solución de problemas

Usa la siguiente información como guía para solucionar problemas durante la validación.

No se dibujó ningún desenfoque

  • Verifica que los desenfoques estén habilitados y que el hardware los admita. Consulta Cómo activar y desactivar el desenfoque de la ventana.

  • Asegúrate de establecer un color de fondo translúcido para la ventana. Un color de fondo opaco de la ventana oculta el área desenfocada.

El dispositivo de prueba no admite desenfoques de ventana.

  • Prueba tu aplicación en el emulador de Android 12. Para configurar un Android Emulator, consulta Configura un Android Emulator. Cualquier dispositivo virtual de Android que crees con el emulador admite los desenfoques de la ventana.

Sin esquinas redondeadas

La actualización de la opción para desarrolladores no habilita los desenfoques.

  • Comprueba si el dispositivo está en modo de ahorro de batería o si usa túneles multimedia. En algunos dispositivos de TV, es posible que también se inhabiliten los desenfoques de la ventana durante la reproducción de video.

Desenfoque de fondo dibujado en pantalla completa, no dentro de los límites de la ventana

Las actualizaciones del objeto de escucha no se aplican en la pantalla.

  • Es posible que las actualizaciones del objeto de escucha se apliquen a una instancia de ventana anterior. Comprueba si la ventana se destruye y se vuelve a crear con la actualización correcta del objeto de escucha.