Compatibilidad con la renderización en ventanas de escritorio

El modo de ventanas de escritorio permite que los usuarios ejecuten varias apps al mismo tiempo en ventanas de apps de tamaño variable para obtener una experiencia versátil similar a la de una computadora de escritorio.

En la figura 1, puedes ver la organización de la pantalla con el modo de ventanas de escritorio habilitado. Información que debes tener en cuenta:

  • Los usuarios pueden ejecutar varias apps en paralelo de forma simultánea.
  • La barra de tareas se encuentra en una posición fija en la parte inferior de la pantalla y muestra las apps en ejecución. Los usuarios pueden fijar apps para acceder a ellas rápidamente.
  • La nueva barra de encabezado personalizable decora la parte superior de cada ventana con controles, como minimizar y maximizar.
Figura 1: Modo de ventanas de escritorio en una tablet

De forma predeterminada, las apps se abren en pantalla completa en las tablets Android. Para iniciar una app en el modo de ventanas de escritorio, mantén presionado el controlador de la ventana que se encuentra en la parte superior de la pantalla y arrástralo dentro de la IU, como se muestra en la figura 2.

Cuando una app está abierta en el modo de ventanas de escritorio, otras apps también se abren en ventanas de escritorio.

Figura 2: Mantén presionado y arrastra el controlador de la ventana de la app para ingresar al modo de ventanas de escritorio.

Los usuarios también pueden invocar el modo de ventanas de escritorio desde el menú que aparece debajo del controlador de la ventana cuando presionas o haces clic en el controlador, o bien usas la combinación de teclas tecla Meta (Windows, Comando o Buscar) + Ctrl + Abajo.

Los usuarios salen del modo de ventanas de escritorio cerrando todas las ventanas activas o tomando el controlador de la ventana en la parte superior de una ventana de escritorio y arrastrando la app a la parte superior de la pantalla. La combinación de teclas Meta + H también sale del modo de ventanas de escritorio y vuelve a ejecutar las apps en pantalla completa.

Para volver al modo de ventanas de escritorio, presiona o haz clic en la tarjeta de espacio de escritorio en la pantalla Recientes.

Optimiza el diseño de tu app para un entorno similar al de una computadora de escritorio

El diseño para una experiencia de escritorio puede diferir significativamente del diseño para dispositivos móviles debido al mayor espacio en pantalla, la precisión de la entrada del mouse y el teclado, y la expectativa de una alta productividad.

Jetpack WindowManager proporciona una API con opiniones para ayudar a los desarrolladores a decidir cuándo mostrar una IU de escritorio, que suele tener una mayor densidad de información, diferentes patrones de navegación y mejores interacciones con el mouse.

lifecycleScope.launch(Dispatchers.Main) {
    lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
        windowInfoTracker.windowEngagementInfo(this@DesktopWindowingActivity)
            .collect { windowEngagementInfo ->
                if(windowEngagementInfo.hasEngagementMode(WindowEngagementInfo.EngagementMode.PRECISE_POINTER)){
                    showDesktopOptimizedUI()
                }else {
                    showTouchOptimizedUI()
                }
        }
    }
}

Para obtener más información, consulta Diseño para computadoras de escritorio.

Modo de cambio de tamaño y compatibilidad

En el modo de ventanas de escritorio, se puede cambiar el tamaño de las apps con orientación bloqueada libremente. Esto significa que, incluso si una actividad está bloqueada en la orientación vertical, los usuarios pueden cambiar el tamaño de la app a una ventana con orientación horizontal.

Figura 3: Cambiar el tamaño de la ventana de una app con solo orientación vertical a horizontal

Las apps declaradas como no redimensionables (es decir, resizeableActivity = false) tienen su IU escalada, pero mantienen la misma relación de aspecto.

Figura 4: La IU de una app que no se puede cambiar de tamaño se ajusta a medida que cambia el tamaño de la ventana.

Las apps de cámara que bloquean la orientación o se declaran como no redimensionables tienen un tratamiento especial para sus visores de cámara: la ventana se puede redimensionar por completo, pero el visor mantiene la misma relación de aspecto. Si se asume que las apps siempre se ejecutan en modo vertical u horizontal, las apps codifican de forma fija o hacen suposiciones que conducen a errores de cálculo de la vista previa o de la orientación o la relación de aspecto de la imagen capturada, lo que genera imágenes estiradas, de costado o al revés.

Hasta que las apps estén listas para implementar visores de cámara completamente responsivos, el tratamiento especial proporciona una experiencia del usuario más básica que mitiga los efectos que pueden causar las suposiciones incorrectas.

Para obtener más información sobre el modo de compatibilidad de las apps de cámara, consulta Modo de compatibilidad del dispositivo.

Figura 5. El visor de la cámara conserva su relación de aspecto a medida que cambia el tamaño de la ventana.

Inserciones de encabezado personalizables

Todas las apps que se ejecutan en el modo de ventanas de escritorio tienen una barra de encabezado, incluso en el modo envolvente. Puedes personalizar esta barra para evitar que se oculte el contenido de tu app y dibujar elementos de la IU personalizados directamente en el espacio del encabezado.

Chrome antes y después de implementar encabezados personalizados.
Figura 6: Chrome antes y después de implementar encabezados personalizados.

Implementación

Para dibujar contenido personalizado en la barra de encabezado, el primer paso es hacer que el fondo de la barra de encabezado sea transparente. Para lograrlo, usa la marca APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND con WindowInsetsController.

window.insetsController?.setSystemBarsAppearance(
    WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND,
    WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND
)

Una vez que la barra de encabezado sea transparente, puedes aplicar diseño al área del encabezado para que coincida con el diseño de tu app. Usa WindowInsets.isCaptionBarVisible para detectar si la barra está presente y aplicar la altura o el padding adecuados a tu diseño.

@OptIn(ExperimentalLayoutApi::class)
@Composable
fun CaptionBar() {
    if (WindowInsets.isCaptionBarVisible) {
        Row(
            modifier = Modifier
                .windowInsetsTopHeight(WindowInsets.captionBar)
                .fillMaxWidth()
                .background(if (isSystemInDarkTheme()) Color.White else Color.Black),
            horizontalArrangement = Arrangement.Center,
            verticalAlignment = Alignment.CenterVertically
        ) {
            Text(
                text = "Caption Bar Title",
                style = MaterialTheme.typography.titleMedium,
                modifier = Modifier.padding(4.dp)
            )
        }
    }
}

  • setSystemBarsAppearance(appearance,mask): Configura el estilo visual de las barras del sistema. El primer parámetro define las marcas de apariencia de destino, mientras que el segundo actúa como una máscara para controlar qué marcas específicas se modifican.

  • windowInsetsTopHeight(): Establece automáticamente la altura de tu elemento componible para que coincida con la barra de encabezado del sistema, lo que ayuda a que tu fondo personalizado complete el área de subtítulos sin codificar los valores de píxeles.

  • WindowInsets.captionBar: Proporciona las dimensiones de los controles de ventanas de escritorio (Cerrar, Maximizar, etc.), lo que permite que la IU se escale o se oculte automáticamente cuando se ingresa o se sale del modo de ventanas de escritorio.

Para obtener más información, consulta Acerca de las inserciones de ventanas. Además de un título, puedes mostrar otros elementos de la IU en la barra de subtítulos, como pestañas (como en Google Chrome), barras de búsqueda o avatares de perfil.

Interfaz de usuario

Para evitar que la IU se superponga con los botones del sistema, Android 15 proporciona el método WindowInsets#getBoundingRects(). El método devuelve una lista de objetos Rect que representan las áreas ocupadas por los elementos del sistema. Cualquier espacio restante en la barra de subtítulos es una zona segura en la que puedes colocar contenido personalizado de forma segura.

Activa o desactiva la apariencia de los elementos de la barra de título del sistema para los temas claro y oscuro con APPEARANCE_LIGHT_CAPTION_BARS. Accede a las inserciones con WindowInsets.Companion.captionBar() en Compose o WindowInsets.Type.captionBar() en Views.

Para obtener más información, consulta Acerca de las inserciones de ventanas.

Compatibilidad con instancias y tareas múltiples

La realización de varias tareas a la vez es el núcleo del modo de ventanas de escritorio, y permitir varias instancias de tu app puede aumentar considerablemente la productividad de los usuarios.

A partir de Android 15, puedes usar PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI. Si configuras esta propiedad en tu AndroidManifest.xml, especificas que la IU del sistema debe proporcionar opciones (como un botón "Nueva ventana") para que la app se inicie en varias instancias.

<application>
    <property
        android:name="android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI"
        android:value="true" />
</application>

Nota: En el modo de ventanas de escritorio y otros entornos multiventana, las tareas nuevas se abren en una ventana nueva, por lo que debes verificar el recorrido del usuario cada vez que tu app inicie varias tareas.

Administra instancias de apps con gestos de arrastre

En el modo multiventana, los usuarios pueden iniciar una nueva instancia de la app arrastrando un elemento de la IU (como una pestaña o un documento) fuera de la ventana de la app. Los usuarios también pueden mover elementos entre diferentes instancias de la misma app.

Figura 7: Inicia una nueva instancia de Chrome arrastrando una pestaña fuera de la ventana de escritorio.

Transfiere datos arrastrando y soltando

Para configurar un elemento de componibilidad como una fuente de arrastre para la función de arrastrar y soltar en instancias múltiples, lo que permite a los usuarios arrastrar contenido a otra instancia de tu app o crear una instancia nueva soltando contenido en un área vacía de la pantalla, usa el modificador dragAndDropSource. En su lambda, devuelve DragAndDropTransferData y pasa el ClipData que contiene los datos que se transferirán y las marcas para configurar el comportamiento de instancias múltiples.

Android 15 presenta dos marcas clave para el modo de ventanas de estilo de escritorio y las interacciones de instancias múltiples:

  • DRAG_FLAG_GLOBAL_SAME_APPLICATION: Indica que una operación de arrastre puede cruzar los límites de la ventana (para varias instancias de la misma aplicación). Cuando se llama a startDragAndDrop() con esta marca establecida, solo las ventanas visibles que pertenecen a la misma aplicación pueden participar en la operación de arrastre y recibir el contenido arrastrado.

Modifier.dragAndDropSource { _ ->
    DragAndDropTransferData(
        clipData = ClipData.newPlainText("label", "Your data"),
        flags = View.DRAG_FLAG_GLOBAL_SAME_APPLICATION
    )
}

Modifier.dragAndDropSource { _ ->
    val intent = Intent.makeMainActivity(activity.componentName).apply {
        putExtra("EXTRA_ITEM_ID", itemId)
        flags = Intent.FLAG_ACTIVITY_NEW_TASK or
                Intent.FLAG_ACTIVITY_MULTIPLE_TASK or
                Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT
    }

    val pendingIntent = PendingIntent.getActivity(
        activity, 0, intent, PendingIntent.FLAG_IMMUTABLE
    )

    val data = ClipData(
        "Item $itemId",
        arrayOf(ClipDescription.MIMETYPE_TEXT_INTENT),
        ClipData.Item.Builder().setIntentSender(pendingIntent.intentSender).build()
    )

    DragAndDropTransferData(
        clipData = data,
        flags = View.DRAG_FLAG_GLOBAL_SAME_APPLICATION or
                View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG,
    )
}

Cómo recibir datos transferidos

Para aceptar datos de otra instancia, usa el modificador dragAndDropTarget. Debes solicitar permisos de forma explícita si los datos provienen de una instancia o app diferente.

Modifier.dragAndDropTarget(
    shouldStartDragAndDrop = { event ->
        event.toAndroidDragEvent().clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)
    },
    target = object : DragAndDropTarget {
        override fun onDrop(event: DragAndDropEvent): Boolean {
            requestDragAndDropPermissions(activity, event.toAndroidDragEvent())
            val clipData = event.toAndroidDragEvent().clipData
            val item = clipData?.getItemAt(0)?.text
            if (item != null) {
                // Process the dropped text item here
            }
            return item != null
        }
    }
)

Pasos clave:

  • Filtro: Usa shouldStartDragAndDrop para verificar si se admiten los datos entrantes (tipo de MIME).
  • Permisos: Llama a requestDragAndDropPermissions(event) para acceder a los datos.
  • Controla: Extrae los datos en la devolución de llamada onDrop.

Optimizaciones adicionales

Personalizar los inicios de las apps y hacer la transición de las apps del modo de ventanas de escritorio a pantalla completa

Especifica el tamaño y la posición predeterminados

No todas las apps, incluso si se puede cambiar su tamaño, necesitan una ventana grande para ofrecer valor al usuario. Puedes usar el método ActivityOptions#setLaunchBounds() para especificar un tamaño y una posición predeterminados cuando se inicia una actividad.

Cómo ingresar a la pantalla completa desde el espacio de escritorio

Las apps pueden pasar a pantalla completa llamando a Activity#requestFullScreenMode(). El método muestra la app en pantalla completa directamente desde el modo de ventanas de escritorio.