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 redimensionables para brindar 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 una al lado de la otra 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 en la parte superior de la pantalla y arrástralo dentro de la IU, como se muestra en la figura 2.

Cuando una app se abre en el modo de ventanas de escritorio, las demás 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 la función de ventanas de escritorio desde el menú que aparece debajo del controlador de la ventana cuando presionan o hacen clic en el controlador, o bien usan la combinación de teclas tecla Meta (Windows, Comando o Búsqueda) + Ctrl + flecha hacia abajo.

Para salir del modo de ventanas de escritorio, los usuarios deben cerrar todas las ventanas activas o tomar el controlador de la ventana en la parte superior de una ventana de escritorio y arrastrar la app a la parte superior de la pantalla. La combinación de teclas Meta + H también cierra las ventanas de escritorio y vuelve a ejecutar las apps en pantalla completa.

Para volver a la ventana de escritorio, presiona o haz clic en la tarjeta del espacio de escritorio en la pantalla Recientes.

Modo de compatibilidad y cambio de tamaño

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

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

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

Figura 4: La IU de una app que no cambia 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 es completamente redimensionable, pero el visor mantiene la misma relación de aspecto. Si se supone que las apps siempre se ejecutan en orientación vertical u horizontal, las apps codifican de forma rígida o realizan suposiciones que conducen a cálculos incorrectos de la orientación o la relación de aspecto de la vista previa o la imagen capturada, lo que genera imágenes estiradas, laterales o invertidas.

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

Para obtener más información sobre el modo de compatibilidad para 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 se cambia el tamaño de la ventana.

Inserciones de encabezado personalizables

Todas las apps que se ejecutan en 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 darle estilo 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 relleno 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 Composable para que coincida con la barra de encabezado del sistema, lo que ayuda a que tu fondo personalizado llene el área de subtítulos sin codificar valores de píxeles.

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

Para obtener más información, consulta Acerca de las inserciones de ventana. 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. El espacio restante en la barra de subtítulos es una zona segura en la que puedes colocar contenido personalizado sin problemas.

Alterna la apariencia de los elementos de subtítulos 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 ventana.

Compatibilidad con instancias y tareas múltiples

La realización de varias tareas a la vez es la base del modo de ventanas de escritorio, y permitir varias instancias de tu app puede aumentar en gran medida 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 "Ventana nueva") 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 la organización de ventanas de escritorio y otros entornos de 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: Arrastra una pestaña fuera de la ventana de escritorio para iniciar una nueva instancia de Chrome.

Transfiere datos con la función de arrastrar y soltar

Para configurar un elemento componible como fuente de arrastre para la función de arrastrar y soltar de varias instancias, 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, pasando el ClipData que contiene los datos que se transferirán y las marcas para configurar el comportamiento de varias instancias.

Android 15 presenta dos marcas clave para el modo de ventanas de escritorio y las interacciones de varias instancias:

  • 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 este parámetro de configuración establecido, 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 otra instancia o app.

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.
  • Controlador: Extrae los datos en la devolución de llamada onDrop.

Optimizaciones adicionales

Personaliza los inicios de apps y la transición de apps desde ventanas de escritorio a pantalla completa.

Cómo especificar el tamaño y la posición predeterminados

No todas las apps, incluso si se pueden cambiar de 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 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.