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 del fondo te permite crear ventanas con fondos desenfocados, lo que genera un efecto de vidrio esmerilado.

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

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

solo desenfoque de fondo

a

desenfocar solo detrás

a

desenfoque detrás y desenfoque de 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 del fondo, un radio de 20 px crea un buen efecto de profundidad de campo, mientras que un radio de desenfoque del 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

Usar el desenfoque del fondo en 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 en verdadero para que la ventana sea translúcida. El desenfoque se dibuja debajo de la superficie de la ventana, por lo que la ventana debe ser translúcida para que el desenfoque sea visible.

  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. También puedes establecer R.attr.windowBackground en el tema de la ventana.

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

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

Desenfoque 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 radio de desenfoque detrás. 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: Los dispositivos con GPU de menor rendimiento pueden optar por no admitir el desenfoque de las ventanas.

  • Estado del sistema: El servidor del sistema puede inhabilitar temporalmente el desenfoque de las ventanas en el tiempo de ejecución, por ejemplo, durante el modo de ahorro de batería, mientras reproduce ciertos tipos de contenido de video o debido a una anulación del desarrollador.

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

  • Agrega un objeto de escucha mediante WindowManager#addCrossWindowBlurEnabledListener para recibir notificaciones cuando el desenfoque de la ventana esté habilitado o inhabilitado. 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 desenfocar el fondo, aumenta el alfa del elemento de diseño del 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 de 20 px y un radio de desenfoque del fondo de 80 px. Tiene esquinas redondeadas, definidas en XML, en el elemento de diseño de fondo de la ventana. Controla correctamente diferentes versiones de Android, diferentes dispositivos (que posiblemente no sean compatibles con el desenfoque de ventanas) y los cambios habilitados o inhabilitados en el tiempo de ejecución. Garantiza que el contenido del diálogo sea legible en cualquiera de esas condiciones ajustando la versión 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 en 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>

El desenfoque de la ventana desenfoca el contenido de la ventana debajo de la actividad. La imagen desenfocada se dibuja debajo de esta ventana de actividad, por lo que esta debe ser translúcida para que el desenfoque sea visible. 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 comprobar si tu dispositivo admite el desenfoque de ventanas, haz lo siguiente:

  • Asegúrate de que el dispositivo pueda manejar la carga adicional de la GPU. Es posible que los dispositivos de gama baja no puedan soportar la carga adicional, lo que puede provocar una disminución de los fotogramas. Habilita el desenfoque de las ventanas solo 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 puede admitir el desenfoque de ventanas, configura el siguiente deslizador de superficie sysprop:

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. Para habilitar o inhabilitar el desenfoque de las ventanas, activa o desactiva el desenfoque de las ventanas.

  3. Verifica que la IU de la ventana cambie hacia y desde el estado de desenfoque como se esperaba.

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 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 acelerada por hardware -> Permitir desenfoques a nivel de ventana

  • Desde la terminal en un dispositivo con permisos de administrador:

    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 lo siguiente como guía para solucionar problemas durante la validación.

Sin difuminar

El dispositivo de prueba no es compatible con el desenfoque de ventanas

  • 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 el desenfoque.

  • Verifica si el dispositivo está en modo de ahorro de batería o si está usando 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 del 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. Verifica si la ventana se destruye y se vuelve a crear con la actualización del objeto de escucha correcta.