Incorporación de actividades

La incorporación de actividades optimiza las apps en dispositivos de pantalla grande mediante la división de la ventana de tareas de una aplicación entre dos actividades o dos instancias de la misma actividad.

Figura 1: App de Configuración con actividades en paralelo.

Si tu app consta de varias actividades, incorporarlas te permitirá proporcionar con facilidad una experiencia del usuario mejorada en tablets, dispositivos plegables y dispositivos ChromeOS.

La incorporación de actividades no requiere refactorización de código. Tú determinas el modo en que tu app muestra sus actividades, una al lado de la otra o apiladas, mediante la creación de un o realiza llamadas a la API de Jetpack WindowManager.

La compatibilidad con pantallas pequeñas se mantiene automáticamente. Cuando tu app está en un dispositivo con una pantalla pequeña, las actividades se apilan una sobre otra. En pantallas grandes, las actividades se muestran una al lado de la otra. El sistema determina la presentación en función de la configuración que creaste: no se requiere lógica de ramificación.

La incorporación de actividades admite cambios de orientación del dispositivo y funciona a la perfección en dispositivos plegables, en los que se modifica la pila de actividades a medida que el dispositivo se pliega y se despliega.

La incorporación de actividades es compatible con la mayoría de los dispositivos con pantalla grande que ejecutan Android 12L (nivel de API 32) y versiones posteriores.

Ventana de tareas dividida

La incorporación de actividades divide la ventana de tareas de la app en dos contenedores: uno principal y otro secundario. Estos contenedores incluyen las actividades iniciadas desde la actividad principal o desde otras que ya se encuentran en ellos.

Las actividades se apilan en el contenedor secundario a medida que se inician secundario se apila sobre el principal en pantallas pequeñas, de modo que la pila de actividades y la navegación hacia atrás sean coherentes con el orden de actividades ya integradas en tu app.

La incorporación de actividades te permite mostrarlas de varias formas. Tu La app puede dividir la ventana de tareas iniciando dos actividades, una al lado de la otra. simultáneamente:

Figura 2: Dos actividades en paralelo.

Alternativamente, una actividad que ocupa toda la ventana de tareas puede crear una división mediante el lanzamiento de una nueva actividad junto a ella:

Figura 3: La actividad A inicia la actividad B al costado.

Las actividades que ya están divididas y comparten una ventana de tareas pueden iniciar otras actividades de las siguientes maneras:

  • Al costado y sobre otra actividad:

    Figura 4: La actividad A inicia la actividad C al costado y sobre la actividad B.
  • Hacia un lado y desplaza la división hacia un lado, ocultando la principal anterior actividad:

    Figura 5: La actividad B inicia la actividad C al costado y mueve la división hacia un lado.
  • Iniciando una actividad en el lugar, sobre la otra, es decir, en la misma pila de actividades:

    Figura 6: La actividad B inicia la actividad C sin marcas de intents adicionales.
  • Iniciando una ventana de actividad completa en la misma tarea:

    Figura 7: La actividad A o la actividad B inician la actividad C, que ocupa la ventana de tareas por completo.

Navegación hacia atrás

Los diferentes tipos de aplicaciones pueden tener distintas reglas de navegación hacia atrás en un estado de ventana de tareas dividida según las dependencias entre las actividades o la forma en que los usuarios activan el evento Atrás, por ejemplo:

  • En conjunto: Si las actividades están relacionadas y una no se debería mostrar sin la otra, se puede configurar la navegación hacia atrás para finalizar ambas.
  • Por separado: Si las actividades son completamente independientes, la navegación hacia atrás en una actividad no afecta el estado de otra actividad en la ventana de tareas.

El evento Atrás se envía a la última actividad enfocada cuando se usa la navegación con botones.

Para la navegación basada en gestos, haz lo siguiente:

  • Android 14 (nivel de API 34) y versiones anteriores: El evento Atrás se envía a la actividad donde ocurrió el gesto. Cuando los usuarios deslizan el dedo desde el lado izquierdo de la pantalla, el evento Atrás se envía a la actividad en el panel izquierdo de la ventana dividida. Cuando los usuarios deslizan el dedo desde el lado derecho de la pantalla, el evento de atrás se envía a la actividad en el panel derecho.

  • Android 15 (nivel de API 35) y versiones posteriores

    • Cuando se trata de varias actividades de la misma app, el gesto finaliza la actividad superior independientemente de la dirección del deslizamiento, lo que proporciona una experiencia más unificada.

    • En situaciones que involucran dos actividades de diferentes apps (superposición), el evento Atrás se dirige a la última actividad en foco, lo que se alinea con el comportamiento de la navegación con botones.

Diseño multipanel

Jetpack WindowManager te permite compilar una incorporación de actividades de varios paneles en dispositivos con pantalla grande con Android 12L (nivel de API 32) o versiones posteriores, y algunos dispositivos con versiones anteriores de la plataforma. Las apps existentes que se basan en varias actividades en lugar de fragmentos o diseños basados en vistas, como SlidingPaneLayout, pueden brindar una experiencia mejorada del usuario de pantallas grandes sin refactorización del código fuente.

Un ejemplo común es una división de lista-detalles. Para garantizar una experiencia presentación, el sistema inicia la actividad de la lista y, luego, la aplicación inicia de inmediato la actividad de detalles. El sistema de transición espera hasta que las actividades se dibujan y, luego, las muestra juntas. Para el usuario, los dos las actividades se lanzan como una sola.

Figura 8: Dos actividades iniciadas simultáneamente en un diseño multipanel

Atributos de división

Puedes especificar cómo se determinará la proporción de la ventana de tareas entre los contenedores divididos y cómo se organizan los contenedores en relación con los demás.

Para las reglas definidas en un archivo de configuración XML, configura los siguientes atributos:

  • splitRatio: Establece las proporciones del contenedor. El valor es un número de punto flotante en el intervalo abierto (0.0, 1.0).
  • splitLayoutDirection: Especifica cómo se organizan los contenedores divididos en relación con los demás. Entre los valores, se incluyen los siguientes:
    • ltr: De izquierda a derecha
    • rtl: De derecha a izquierda
    • locale: Se determina ltr o rtl a partir de la configuración regional

Consulta la sección Configuración XML para ver ejemplos.

Para las reglas creadas con las APIs de WindowManager, crea un SplitAttributes. objeto con SplitAttributes.Builder y llama al siguiente compilador métodos:

Consulta la sección API de WindowManager para ver ejemplos.

Figura 9: Dos divisiones de actividad distribuidas de izquierda a derecha, pero con diferentes relaciones de división

Marcadores de posición

Las actividades de marcador de posición son actividades secundarias vacías que ocupan un área de una división de actividad. En definitiva, deben reemplazarse por otra actividad que incluye contenido. Por ejemplo, una actividad de marcador de posición puede ocupar la lado secundario de una actividad dividida en un diseño de lista-detalles hasta que un elemento de se selecciona la lista y, a partir de ese momento, se muestra una actividad que contiene los detalles información del elemento de lista seleccionado reemplaza el marcador de posición.

De forma predeterminada, el sistema muestra marcadores de posición solo cuando hay suficiente espacio para una división de actividad. Los marcadores de posición terminan automáticamente cuando el tamaño de visualización cambia a un ancho o una altura demasiado pequeños para mostrar una división. Cuando el espacio lo permite, el sistema reinicia el marcador de posición con un estado de nueva inicialización.

Figura 10: Plegado y desplegado de un dispositivo plegable. La actividad del marcador de posición finaliza y se vuelve a crear cuando cambia el tamaño de la pantalla.

Sin embargo, el atributo stickyPlaceholder de un objeto SplitPlaceholderRule o setSticky() de SplitPlaceholder.Builder puede anular la comportamiento predeterminado. Cuando el atributo o el método especifican un valor de true, el sistema muestra el marcador de posición como la actividad superior en la ventana de tareas cuando el tamaño de la pantalla se reduce de dos paneles a uno solo (consulta Configuración de división para ver un ejemplo).

Figura 11: Plegado y desplegado de un dispositivo plegable. La actividad del marcador de posición es fija.

Cambios en el tamaño de la ventana

Cuando los cambios en la configuración del dispositivo reducen el ancho de la ventana de tareas para que no sea lo suficientemente grande para un diseño de varios paneles (por ejemplo, cuando un dispositivo plegable de pantalla grande se pliega del tamaño de una tablet al de un teléfono o se cambia el tamaño de la ventana de la app en el modo multiventana), las actividades que no son de marcador de posición en el panel secundario de la ventana de tareas se apilan sobre las actividades del panel principal.

Las actividades de marcadores de posición solo se muestran cuando el ancho de la pantalla es el suficiente para realizar una división. En pantallas más pequeñas, el marcador de posición se descarta automáticamente. Cuando el área de visualización vuelve a ser lo bastante grande, se vuelve a crear el marcador de posición. (Consulta la sección Marcadores de posición).

Es posible apilar las actividades porque WindowManager aplica el orden Z a las actividades del panel secundario sobre las actividades del panel principal.

Varias actividades en el panel secundario

La actividad B inicia la actividad C en el lugar, sin marcas de intents adicionales:

División de actividad que contiene las actividades A, B y C, con C apilada sobre B.

Esto da como resultado el siguiente orden Z de las actividades en la misma tarea:

Pila secundaria de actividades que contiene la actividad C apilada sobre B.
          La pila secundaria se apila sobre la principal que contiene la actividad A.

Por lo tanto, en una ventana de tareas más pequeña, la aplicación se reduce a una sola actividad, con C en la parte superior de la pila:

Ventana pequeña que muestra solo la actividad C.

Si vuelves a la ventana más pequeña, podrás navegar por las actividades apiladas unas sobre otras.

Si se restablece la configuración de la ventana de tareas a un tamaño más grande que admite varios paneles, las actividades se volverán a mostrar una al lado de la otra.

Divisiones apiladas

La actividad B inicia la actividad C al costado y mueve la división hacia un lado.

Ventana de tareas que muestra las actividades A y B, y, luego, las actividades B y C.

El resultado es el siguiente orden Z de las actividades en la misma tarea:

Actividades A, B y C en una sola pila. Las actividades se apilan en el siguiente orden de arriba hacia abajo: C, B y A.

En una ventana de tareas más pequeña, la aplicación se reduce a una sola actividad, con C en la parte superior:

Ventana pequeña que muestra solo la actividad C.

Orientación vertical fija

La configuración del manifiesto android:screenOrientation permite a las apps restringir a la orientación vertical u horizontal. Para mejorar la experiencia del usuario en dispositivos de pantalla grande, como tablets y dispositivos plegables, los fabricantes de dispositivos (OEMs) pueden ignorar las solicitudes de orientación de la pantalla y colocar la app en formato letterbox en orientación vertical en pantallas horizontales u orientación horizontal en pantallas verticales.

Figura 12: Actividades en formato letterbox: orientación vertical fija en dispositivo horizontal (izquierda) y orientación horizontal fija en dispositivo vertical (derecha)

De manera similar, cuando se habilita la incorporación de actividades, los OEMs pueden personalizar los dispositivos para actividades en formato letterbox con orientación vertical fija en orientación horizontal en pantallas grandes (ancho ≥600 dp). Cuando una actividad con formato vertical fijo inicia una segunda actividad, el dispositivo puede mostrar ambas actividades en paralelo en una pantalla con dos paneles.

Figura 13: La actividad con orientación vertical fija A inicia la actividad B al costado.

Agrega siempre la propiedad android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED al archivo de manifiesto de tu app para informar a los dispositivos que tu app admite la incorporación de actividades (consulta la sección Configuración de divisiones). Los dispositivos personalizados para OEMs pueden determinar si se usa el formato letterbox con orientación vertical fija.

Configuración de divisiones

Las divisiones de actividad se configuran con las reglas de división. Las reglas de división se definen en un archivo archivo de configuración o haciendo que la API de WindowManager de Jetpack llamadas.

En cualquier caso, tu app debe acceder a la biblioteca de WindowManager y debe informar el sistema en el que la app implementó la incorporación de actividades.

Haz lo siguiente:

  1. Agrega la dependencia más reciente de la biblioteca de WindowManager al nivel de módulo de tu app build.gradle, por ejemplo:

    implementation 'androidx.window:window:1.1.0-beta02'

    La biblioteca de WindowManager proporciona todos los componentes necesarios para la incorporación de actividades.

  2. Informa al sistema que tu app implementó la incorporación de actividades.

    Agrega la propiedad android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED a la <application> del archivo de manifiesto de la app y configura value como true, por ejemplo:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android">
        <application>
            <property
                android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"
                android:value="true" />
        </application>
    </manifest>
    

    En WindowManager versión 1.1.0-alpha06 y posteriores, la incorporación de actividades se divide se inhabilitan, a menos que la propiedad se agregue al manifiesto y se configure como verdadera.

    Además, los fabricantes de dispositivos usan el parámetro de configuración para habilitar capacidades personalizadas para las apps que admiten la incorporación de actividades. Por ejemplo, los dispositivos pueden utilizar formato letterbox en una actividad exclusiva del modo vertical en el modo horizontal para la transición a un diseño de panel dual cuando se inicia una segunda actividad (consulta orientación vertical fija).

Configuración de XML

Para crear una implementación basada en XML de incorporación de actividad, completa los siguientes pasos:

  1. Crea un archivo de recursos XML que realice las siguientes acciones:

    • Definir las actividades que comparten una división
    • Configurar las opciones de división
    • Crea un marcador de posición para el contenedor secundario de la división cuando el contenido no está disponible.
    • Especificar las actividades que nunca deben ser parte de una división

    Por ejemplo:

    <!-- main_split_config.xml -->
    
    <resources
        xmlns:window="http://schemas.android.com/apk/res-auto">
    
        <!-- Define a split for the named activities. -->
        <SplitPairRule
            window:splitRatio="0.33"
            window:splitLayoutDirection="locale"
            window:splitMinWidthDp="840"
            window:splitMaxAspectRatioInPortrait="alwaysAllow"
            window:finishPrimaryWithSecondary="never"
            window:finishSecondaryWithPrimary="always"
            window:clearTop="false">
            <SplitPairFilter
                window:primaryActivityName=".ListActivity"
                window:secondaryActivityName=".DetailActivity"/>
        </SplitPairRule>
    
        <!-- Specify a placeholder for the secondary container when content is
             not available. -->
        <SplitPlaceholderRule
            window:placeholderActivityName=".PlaceholderActivity"
            window:splitRatio="0.33"
            window:splitLayoutDirection="locale"
            window:splitMinWidthDp="840"
            window:splitMaxAspectRatioInPortrait="alwaysAllow"
            window:stickyPlaceholder="false">
            <ActivityFilter
                window:activityName=".ListActivity"/>
        </SplitPlaceholderRule>
    
        <!-- Define activities that should never be part of a split. Note: Takes
             precedence over other split rules for the activity named in the
             rule. -->
        <ActivityRule
            window:alwaysExpand="true">
            <ActivityFilter
                window:activityName=".ExpandedActivity"/>
        </ActivityRule>
    
    </resources>
    
  2. Crear un inicializador.

    El componente RuleController de WindowManager analiza el XML de Terraform y pone las reglas a disposición del sistema. Un propulsor La biblioteca de Startup Initializer pone el archivo en formato XML a disposición del RuleController durante el inicio de la app para que las reglas se apliquen cuando antes de comenzar las actividades.

    Para crear un inicializador, haz lo siguiente:

    1. Agrega la dependencia más reciente de la biblioteca de Jetpack Startup a tu nivel de módulo. build.gradle, por ejemplo:

      implementation 'androidx.startup:startup-runtime:1.1.1'

    2. Crea una clase que implemente la interfaz Initializer.

      El inicializador pone a disposición las reglas de división para RuleController. y pasa el ID del archivo de configuración XML (main_split_config.xml). al método RuleController.parseRules().

      Kotlin

      class SplitInitializer : Initializer<RuleController> {
      
          override fun create(context: Context): RuleController {
              return RuleController.getInstance(context).apply {
                  setRules(RuleController.parseRules(context, R.xml.main_split_config))
              }
          }
      
          override fun dependencies(): List<Class<out Initializer<*>>> {
              return emptyList()
          }
      }

      Java

      public class SplitInitializer implements Initializer<RuleController> {
      
           @NonNull
           @Override
           public RuleController create(@NonNull Context context) {
               RuleController ruleController = RuleController.getInstance(context);
               ruleController.setRules(
                   RuleController.parseRules(context, R.xml.main_split_config)
               );
               return ruleController;
           }
      
           @NonNull
           @Override
           public List<Class<? extends Initializer<?>>> dependencies() {
               return Collections.emptyList();
           }
      }
  3. Crea un proveedor de contenido para las definiciones de la regla.

    Agrega androidx.startup.InitializationProvider al archivo de manifiesto de tu app. como <provider>. Incluye una referencia a la implementación de tu Iniciador de RuleController, SplitInitializer:

    <!-- AndroidManifest.xml -->
    
    <provider android:name="androidx.startup.InitializationProvider"
        android:authorities="${applicationId}.androidx-startup"
        android:exported="false"
        tools:node="merge">
        <!-- Make SplitInitializer discoverable by InitializationProvider. -->
        <meta-data android:name="${applicationId}.SplitInitializer"
            android:value="androidx.startup" />
    </provider>
    

    InitializationProvider descubre e inicializa SplitInitializer antes Se llama al método onCreate() de la app. Como resultado, las reglas de división están en efecto cuando comienza la actividad principal de la app.

API de WindowManager

Puedes implementar la incorporación de actividades de manera programática con varias APIs. llamadas. Realiza las llamadas en el método onCreate() de una subclase de Application para asegurarte de que las reglas estén vigentes antes de que se inicien las actividades.

Para crear una división de actividad de manera programática, haz lo siguiente:

  1. Crea una regla de división:

    1. Crea un SplitPairFilter que identifique las actividades que comparten la división:

      Kotlin

      val splitPairFilter = SplitPairFilter(
         ComponentName(this, ListActivity::class.java),
         ComponentName(this, DetailActivity::class.java),
         null
      )

      Java

      SplitPairFilter splitPairFilter = new SplitPairFilter(
         new ComponentName(this, ListActivity.class),
         new ComponentName(this, DetailActivity.class),
         null
      );
    2. Agrega el filtro a un conjunto de filtros:

      Kotlin

      val filterSet = setOf(splitPairFilter)

      Java

      Set<SplitPairFilter> filterSet = new HashSet<>();
      filterSet.add(splitPairFilter);
    3. Crea atributos de diseño para la división:

      Kotlin

      val splitAttributes: SplitAttributes = SplitAttributes.Builder()
          .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
          .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
          .build()

      Java

      final SplitAttributes splitAttributes = new SplitAttributes.Builder()
            .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
            .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
            .build();

      SplitAttributes.Builder crea un objeto que contiene atributos de diseño:

      • setSplitType(): Define cómo se encuentra el área de visualización disponible. asignados a cada contenedor de actividades. El tipo de división de proporción especifica la proporción del área de visualización disponible asignada al contenedor principal; el contenedor secundario ocupa el resto el área de visualización disponible.
      • setLayoutDirection(): Especifica cómo se almacenan los contenedores de actividades. se distribuyen en relación con los demás, con el contenedor principal primero.
    4. Compila una SplitPairRule:

      Kotlin

      val splitPairRule = SplitPairRule.Builder(filterSet)
          .setDefaultSplitAttributes(splitAttributes)
          .setMinWidthDp(840)
          .setMinSmallestWidthDp(600)
          .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
          .setFinishPrimaryWithSecondary(SplitRule.FinishBehavior.NEVER)
          .setFinishSecondaryWithPrimary(SplitRule.FinishBehavior.ALWAYS)
          .setClearTop(false)
          .build()

      Java

      SplitPairRule splitPairRule = new SplitPairRule.Builder(filterSet)
          .setDefaultSplitAttributes(splitAttributes)
          .setMinWidthDp(840)
          .setMinSmallestWidthDp(600)
          .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
          .setFinishPrimaryWithSecondary(SplitRule.FinishBehavior.NEVER)
          .setFinishSecondaryWithPrimary(SplitRule.FinishBehavior.ALWAYS)
          .setClearTop(false)
          .build();

      SplitPairRule.Builder crea y configura la regla:

      • filterSet: Contiene filtros de par dividido que determinan cuándo se debe aplicar la regla mediante la identificación de actividades que comparten una división.
      • setDefaultSplitAttributes(): Aplica los atributos de diseño a la regla.
      • setMinWidthDp(): Establece el ancho de pantalla mínimo (en píxeles independientes de la densidad, dp) que permite una división.
      • setMinSmallestWidthDp(): Establece el valor mínimo (en dp) que debe tener la menor de las dos dimensiones de pantalla para permitir una división, independientemente de la orientación del dispositivo.
      • setMaxAspectRatioInPortrait(): Establece el aspecto máximo de la pantalla. relación (altura:ancho) en orientación vertical para la que la actividad se muestran divisiones. Si la relación de aspecto de una pantalla vertical supera la relación de aspecto máxima, se inhabilitan las divisiones, independientemente del ancho de la pantalla. Nota: El valor predeterminado es 1.4, da como resultado actividades que ocupan toda la ventana de tareas en orientación vertical orientación en la mayoría de las tablets. Consulta también SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT y setMaxAspectRatioInLandscape(). El valor predeterminado para la orientación horizontal es ALWAYS_ALLOW.
      • setFinishPrimaryWithSecondary(): Establece cómo finalizar todo las actividades en el contenedor secundario afecta a las actividades en el contenedor principal. NEVER indica que el sistema no debe finalizar las actividades principales cuando terminan todas las actividades del contenedor secundario (consulta Finalizar actividades).
      • setFinishSecondaryWithPrimary(): Establece cómo el hecho de finalizar todas las actividades del contenedor principal afecta las actividades del contenedor secundario. ALWAYS indica que el sistema siempre debe finalizar las actividades en el contenedor secundario cuando todas las actividades en el final del contenedor principal (consulta Finalizar actividades).
      • setClearTop(): Especifica si todas las actividades del contenedor secundario finalizan cuando se inicia una nueva actividad en el contenedor. Un valor false especifica que las actividades nuevas se apilan sobre las que ya se encuentran en el contenedor secundario.
    5. Obtén la instancia singleton de WindowManager RuleController. y agrega la regla:

      Kotlin

        val ruleController = RuleController.getInstance(this)
        ruleController.addRule(splitPairRule)
        

      Java

        RuleController ruleController = RuleController.getInstance(this);
        ruleController.addRule(splitPairRule);
        
  2. Crea un marcador de posición para el contenedor secundario cuando contenido no disponible:

    1. Crea un ActivityFilter que identifique la actividad con la cual el marcador de posición comparte una ventana de tareas dividida:

      Kotlin

      val placeholderActivityFilter = ActivityFilter(
          ComponentName(this, ListActivity::class.java),
          null
      )

      Java

      ActivityFilter placeholderActivityFilter = new ActivityFilter(
          new ComponentName(this, ListActivity.class),
          null
      );
    2. Agrega el filtro a un conjunto de filtros:

      Kotlin

      val placeholderActivityFilterSet = setOf(placeholderActivityFilter)

      Java

      Set<ActivityFilter> placeholderActivityFilterSet = new HashSet<>();
      placeholderActivityFilterSet.add(placeholderActivityFilter);
    3. Crea un SplitPlaceholderRule:

      Kotlin

      val splitPlaceholderRule = SplitPlaceholderRule.Builder(
            placeholderActivityFilterSet,
            Intent(context, PlaceholderActivity::class.java)
          ).setDefaultSplitAttributes(splitAttributes)
           .setMinWidthDp(840)
           .setMinSmallestWidthDp(600)
           .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
           .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS)
           .setSticky(false)
           .build()

      Java

      SplitPlaceholderRule splitPlaceholderRule = new SplitPlaceholderRule.Builder(
            placeholderActivityFilterSet,
            new Intent(context, PlaceholderActivity.class)
          ).setDefaultSplitAttributes(splitAttributes)
           .setMinWidthDp(840)
           .setMinSmallestWidthDp(600)
           .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
           .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS)
           .setSticky(false)
           .build();

      SplitPlaceholderRule.Builder crea y configura la regla:

      • placeholderActivityFilterSet: Contiene filtros de actividad que determinar cuándo aplicar la regla identificando actividades con en el que se asocia la actividad del marcador de posición.
      • Intent: Especifica el inicio de la actividad del marcador de posición.
      • setDefaultSplitAttributes(): Aplica atributos de diseño a la regla.
      • setMinWidthDp(): Establece el ancho mínimo de la pantalla (en píxeles independientes de la densidad, dp). que permite una división.
      • setMinSmallestWidthDp(): Establece el valor mínimo (en dp) que mostrará el más pequeño de los dos. dimensiones deben permitir una división, independientemente del dispositivo orientación.
      • setMaxAspectRatioInPortrait(): Establece la relación de aspecto máxima de la pantalla (altura:ancho) en orientación vertical para la que se muestran las divisiones de actividad. Nota: El el valor predeterminado es 1.4, lo que da como resultado actividades que completan la tarea. en orientación vertical en la mayoría de las tablets. Consulta también SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT y setMaxAspectRatioInLandscape() El valor predeterminado para la orientación horizontal es ALWAYS_ALLOW.
      • setFinishPrimaryWithPlaceholder(): Establece cómo el hecho de finalizar la actividad del marcador de posición afecta a las actividades del contenedor principal. SIEMPRE indica que el sistema siempre debe finalizar las actividades del contenedor principal cuando termina el marcador de posición (consulta Finalizar actividades).
      • setSticky(): Determina si la actividad del marcador de posición aparece en la parte superior de la pila de actividades en pantallas pequeñas una vez que El marcador de posición apareció por primera vez en una división con una cantidad mínima ancho.
    4. Agrega la regla a RuleController de WindowManager:

      Kotlin

      ruleController.addRule(splitPlaceholderRule)

      Java

      ruleController.addRule(splitPlaceholderRule);
  3. Especifica las actividades que nunca deben ser parte de una división:

    1. Crea un ActivityFilter que identifique una actividad que debería siempre ocupan toda el área de visualización de las tareas:

      Kotlin

      val expandedActivityFilter = ActivityFilter(
        ComponentName(this, ExpandedActivity::class.java),
        null
      )

      Java

      ActivityFilter expandedActivityFilter = new ActivityFilter(
        new ComponentName(this, ExpandedActivity.class),
        null
      );
    2. Agrega el filtro a un conjunto de filtros:

      Kotlin

      val expandedActivityFilterSet = setOf(expandedActivityFilter)

      Java

      Set<ActivityFilter> expandedActivityFilterSet = new HashSet<>();
      expandedActivityFilterSet.add(expandedActivityFilter);
    3. Crea una ActivityRule:

      Kotlin

      val activityRule = ActivityRule.Builder(expandedActivityFilterSet)
          .setAlwaysExpand(true)
          .build()

      Java

      ActivityRule activityRule = new ActivityRule.Builder(
          expandedActivityFilterSet
      ).setAlwaysExpand(true)
       .build();

      ActivityRule.Builder crea y configura la regla:

      • expandedActivityFilterSet: Contiene filtros de actividad que determinan cuándo aplicar la regla mediante la identificación de actividades que deseas excluir de las divisiones.
      • setAlwaysExpand(): Especifica si la actividad debe ocupar toda la ventana de tareas.
    4. Agrega la regla a RuleController de WindowManager:

      Kotlin

      ruleController.addRule(activityRule)

      Java

      ruleController.addRule(activityRule);

Incorporación en varias aplicaciones

En Android 13 (nivel de API 33) y versiones posteriores, las apps pueden incorporar actividades de otras apps. La incorporación de actividades entre apps o UID permite la integración visual de actividades de varias aplicaciones para Android. El sistema muestra una actividad de la app host y una actividad incorporada de otra app en pantalla lado a lado o en las partes superior e inferior como en la incorporación de actividades de una app individual.

Por ejemplo, la app de Configuración podría incorporar la actividad del selector de fondo de pantalla de la app de WallpaperPicker:

Figura 14: App de Configuración (menú de la izquierda) con el selector de fondo de pantalla como actividad incorporada (derecha)

Modelo de confianza

Los procesos de host que incorporan actividades de otras apps pueden redefinir la una presentación de las actividades incorporadas, incluidos el tamaño, la posición, el recorte para mantener la transparencia de la organización. Los hosts maliciosos pueden usar esta función para engañar a los usuarios y crear clickjacking u otros ataques de compensación de la IU.

Para evitar el uso inadecuado de la incorporación de actividades entre apps, Android exige que las apps habiliten la opción para permitir la incorporación de sus actividades. Las apps pueden designar hosts como de confianza o poco confiables.

Hosts de confianza

Para permitir que otras aplicaciones incorporen y controlen por completo la presentación de actividades desde tu app, especifica el certificado SHA-256 de la aplicación host en el atributo android:knownActivityEmbeddingCerts de <activity> o los elementos <application> del archivo de manifiesto de tu app.

Configura el valor de android:knownActivityEmbeddingCerts como una cadena:

<activity
    android:name=".MyEmbeddableActivity"
    android:knownActivityEmbeddingCerts="@string/known_host_certificate_digest"
    ... />

O, para especificar varios certificados, un array de cadenas:

<activity
    android:name=".MyEmbeddableActivity"
    android:knownActivityEmbeddingCerts="@array/known_host_certificate_digests"
    ... />

Que hace referencia a un recurso como el siguiente:

<resources>
    <string-array name="known_host_certificate_digests">
      <item>cert1</item>
      <item>cert2</item>
      ...
    </string-array>
</resources>

Para obtener un resumen de certificados SHA, los propietarios de la app pueden ejecutar la tarea signingReport de Gradle. El resumen del certificado es la huella digital SHA-256 sin los dos puntos separados. Para obtener más información, consulta Cómo ejecutar un informe de firma. Autenticación del Cliente.

Hosts no confiables

Para permitir que cualquier app incorpore las actividades de tu app y controle su presentación, especifica el atributo android:allowUntrustedActivityEmbedding en los elementos <activity> o <application> del manifiesto de la app, por ejemplo:

<activity
    android:name=".MyEmbeddableActivity"
    android:allowUntrustedActivityEmbedding="true"
    ... />

El valor predeterminado del atributo es "false", lo cual impide que se incorpore la actividad en diferentes apps.

Autenticación personalizada

Para mitigar los riesgos de incorporación de actividad no confiable, crea un mecanismo de autenticación personalizado que verifique la identidad del host. Si conoces al host certificados, usa la biblioteca androidx.security.app.authenticator para autenticarse. Si el host se autentica después de incorporar tu actividad, puedes muestran el contenido real. De lo contrario, puedes informarle al usuario que se realizó la acción no permitido y bloquear el contenido.

Usa el método ActivityEmbeddingController#isActivityEmbedded() de la biblioteca de WindowManager de Jetpack para verificar si un host incorpora tu actividad, por ejemplo:

Kotlin

fun isActivityEmbedded(activity: Activity): Boolean {
    return ActivityEmbeddingController.getInstance(this).isActivityEmbedded(activity)
}

Java

boolean isActivityEmbedded(Activity activity) {
    return ActivityEmbeddingController.getInstance(this).isActivityEmbedded(activity);
}

Restricción de tamaño mínimo

El sistema Android aplica la altura y el ancho mínimos especificados en el elemento <layout> del manifiesto de la app a las actividades incorporadas. Si una aplicación no especifica la altura y el ancho mínimos, se aplican los valores predeterminados del sistema (sw220dp).

Si el host intenta cambiar el tamaño del contenedor incorporado a un tamaño inferior al como mínimo, el contenedor incorporado se expande para ocupar todos los límites de la tarea.

<activity-alias>

Para que la incorporación de actividades confiables o no confiables funcione con el elemento <activity-alias>, android:knownActivityEmbeddingCerts o Se debe aplicar android:allowUntrustedActivityEmbedding a la actividad objetivo en lugar del alias. La política que verifica la seguridad en el servidor del sistema se basa en las marcas establecidas en el objetivo, no en el alias.

Aplicación de host

Las aplicaciones de host implementan la incorporación de actividades entre apps de la misma manera en que implementar la incorporación de actividades en una sola app. SplitPairRule y Objetos SplitPairFilter o ActivityRule y ActivityFilter especificar actividades incorporadas y divisiones de la ventana de tareas. Las reglas de división se definen de manera estática en XML o en el tiempo de ejecución mediante las llamadas a la API de WindowManager de Jetpack.

Si una aplicación de host intenta incorporar una actividad que no habilitó la incorporación entre apps, la actividad ocupará todos los límites de la tarea. Como resultado, las aplicaciones host deben saber si las actividades objetivo permiten a través de la incorporación de texto.

Si una actividad incorporada comienza una nueva actividad en la misma tarea y no se habilitó la incorporación entre apps, la actividad ocupará todos los límites de la tarea en lugar de superponerse en el contenedor incorporado.

Una aplicación de host puede incorporar sus propias actividades sin restricciones, siempre y cuando las actividades se inicien en la misma tarea.

Ejemplos de divisiones

División desde la ventana completa

Figura 15: La actividad A inicia la actividad B al costado.

No es necesario refactorizar. Puedes definir la configuración de la división de manera estática o en el tiempo de ejecución y, luego, llamar a Context#startActivity() sin ningún parámetro adicional.

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

División predeterminada

Cuando la página de destino de una aplicación está diseñada para dividirse en dos contenedores en pantallas grandes, la experiencia del usuario resulta óptima si ambas actividades se crean y se presentan de forma simultánea. Sin embargo, es posible que el contenido disponible para el contenedor secundario de la división hasta que el usuario interactúe con la actividad en el contenedor principal (por ejemplo, el usuario selecciona un elemento desde un menú de navegación). Una actividad de marcador de posición puede llenar el vacío hasta que el contenido se puede mostrar en el contenedor secundario de la división (consulta el Marcadores de posición).

Figura 16: División que se crea mediante la apertura simultánea de dos actividades. Una actividad es un marcador de posición.

Para crear una división con un marcador de posición, crea un marcador de posición y asócialo con la actividad principal:

<SplitPlaceholderRule
    window:placeholderActivityName=".PlaceholderActivity">
    <ActivityFilter
        window:activityName=".MainActivity"/>
</SplitPlaceholderRule>

Cuando una app recibe un intent, la actividad objetivo se puede mostrar como la parte secundaria de una división de actividad (por ejemplo, una solicitud para mostrar una pantalla de detalles con información sobre un elemento de una lista). En pantallas pequeñas, el detalle se muestra en la ventana de tareas completa; en dispositivos más grandes, junto a la lista.

Figura 17: Actividad de detalles de vínculos directos que se muestra sola en una pantalla pequeña, pero junto con una actividad de lista en una pantalla grande.

La solicitud de inicio se debe enrutar a la actividad principal, y la actividad objetivo de detalles debe iniciarse en una división. El sistema elige automáticamente la presentación correcta (apilada o en paralelo) según el ancho de la pantalla disponible.

Kotlin

override fun onCreate(savedInstanceState Bundle?) {
    . . .
    RuleController.getInstance(this)
        .addRule(SplitPairRule.Builder(filterSet).build())
    startActivity(Intent(this, DetailActivity::class.java))
}

Java

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    . . .
    RuleController.getInstance(this)
        .addRule(new SplitPairRule.Builder(filterSet).build());
    startActivity(new Intent(this, DetailActivity.class));
}

El destino del vínculo directo puede ser la única actividad que debe estar disponible para el usuario en la pila de navegación hacia atrás, y te recomendamos que evites descartar la actividad de detalles y dejar solo la actividad principal:

Pantalla grande con la actividad de lista y la actividad de detalles en paralelo.
          La navegación hacia atrás no puede descartar la actividad de detalles y dejar la actividad de lista en pantalla.

Pantalla pequeña solo con la actividad de detalles. La navegación hacia atrás no puede descartar la actividad de detalles y mostrar la actividad de lista.

En su lugar, puedes finalizar ambas actividades al mismo tiempo mediante el atributo finishPrimaryWithSecondary:

<SplitPairRule
    window:finishPrimaryWithSecondary="always">
    <SplitPairFilter
        window:primaryActivityName=".ListActivity"
        window:secondaryActivityName=".DetailActivity"/>
</SplitPairRule>

Consulta la sección Atributos de configuración.

Varias actividades en contenedores divididos

Apilar varias actividades en un contenedor dividido permite a los usuarios acceder a contenido específico. Por ejemplo, con una división de lista-detalles, es posible que el usuario deba entrar en un de la sección de subdetalles, pero mantén la actividad principal en su lugar:

Figura 18: Actividad abierta en el lugar, en el panel secundario de la ventana de tareas.

Kotlin

class DetailActivity {
    . . .
    fun onOpenSubDetail() {
      startActivity(Intent(this, SubDetailActivity::class.java))
    }
}

Java

public class DetailActivity {
    . . .
    void onOpenSubDetail() {
        startActivity(new Intent(this, SubDetailActivity.class));
    }
}

La actividad de detalles secundarios se coloca sobre la actividad de detalles, y la oculta:

Luego, el usuario puede regresar al nivel anterior de detalles navegando hacia atrás en la pila:

Figura 19: Se quitó la actividad de la parte superior de la pila.

El apilado de actividades una encima de la otra es el comportamiento predeterminado cuando las actividades se inician desde una actividad en el mismo contenedor secundario. Actividades que se inician desde el contenedor principal en una división activa un contenedor secundario en la parte superior de la pila de actividades.

Actividades en una tarea nueva

Cuando las actividades en una ventana dividida de tareas inician actividades en una tarea nueva, esta última es independiente de la que incluye la división y se muestra en una ventana completa. En la pantalla Recents, se muestran dos tareas: la que está en la división y la nueva.

Figura 20: Se inicia la actividad C en una tarea nueva desde la actividad B.

Reemplazo de actividades

Las actividades pueden reemplazarse en la pila del contenedor secundario. por ejemplo, cuando la actividad principal se usa para la navegación de nivel superior y la actividad secundaria es un destino seleccionado. Cada selección de la navegación de nivel superior debe iniciar una actividad nueva en el contenedor secundario y quitar las actividades que antes estaban allí.

Figura 21: La actividad de navegación de nivel superior del panel principal reemplaza las actividades de destino del panel secundario.

La navegación hacia atrás puede resultar confusa cuando se contrae la división (cuando se pliega el dispositivo) si la app no finaliza la actividad en el contenedor secundario cuando cambia la selección de navegación. Por ejemplo, si tienes un menú en el panel principal y las pantallas A y B apiladas en el panel secundario, cuando el usuario pliegue el teléfono, B estará sobre A, y A estará sobre el menú. Cuando el usuario navega hacia atrás desde B, aparece A en lugar del menú.

En esos casos, se debe quitar la pantalla A de la pila de actividades.

El comportamiento predeterminado cuando se realiza un inicio lateral en un contenedor nuevo en un una división existente es colocar los nuevos contenedores secundarios en la parte superior y conservar el en la pila de actividades. Puedes configurar las divisiones de modo que se borren los contenedores secundarios anteriores mediante clearTop y que se inicien las actividades nuevas con normalidad.

<SplitPairRule
    window:clearTop="true">
    <SplitPairFilter
        window:primaryActivityName=".Menu"
        window:secondaryActivityName=".ScreenA"/>
    <SplitPairFilter
        window:primaryActivityName=".Menu"
        window:secondaryActivityName=".ScreenB"/>
</SplitPairRule>

Kotlin

class MenuActivity {
    . . .
    fun onMenuItemSelected(selectedMenuItem: Int) {
        startActivity(Intent(this, classForItem(selectedMenuItem)))
    }
}

Java

public class MenuActivity {
    . . .
    void onMenuItemSelected(int selectedMenuItem) {
        startActivity(new Intent(this, classForItem(selectedMenuItem)));
    }
}

Como alternativa, usa la misma actividad secundaria y desde la principal (menú) La actividad envía intents nuevos que se resuelven en la misma instancia, pero activan un estado. o la actualización de la IU en el contenedor secundario.

Divisiones múltiples

Las apps pueden proporcionar navegación profunda de varios niveles iniciando actividades adicionales a un lado.

Cuando una actividad en un contenedor secundario inicia una actividad nueva a un costado, se crea una división nueva sobre la existente.

Figura 22: La actividad B inicia la actividad C al costado.

La pila de actividades contiene todas las actividades que se abrieron antes, por lo que los usuarios pueden navegar a la división A/B después de finalizar C.

Actividades A, B y C en una pila. Las actividades se apilan en el siguiente orden de arriba hacia abajo: C, B y A.

Para crear una nueva división, inicia la actividad nueva al lado del contenedor secundario existente. Declarar la configuración para las divisiones A/B y B/C y, luego, iniciar la actividad C normalmente desde B:

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
    <SplitPairFilter
        window:primaryActivityName=".B"
        window:secondaryActivityName=".C"/>
</SplitPairRule>

Kotlin

class B {
    . . .
    fun onOpenC() {
        startActivity(Intent(this, C::class.java))
    }
}

Java

public class B {
    . . .
    void onOpenC() {
        startActivity(new Intent(this, C.class));
    }
}

Cómo reaccionar a los cambios de estado de divisiones

Las diferentes actividades de una app pueden tener elementos de la IU que realizan la misma función (por ejemplo, un control que abre una ventana que contiene la configuración de la cuenta).

Figura 23: Diferentes actividades con elementos de la IU funcionalmente idénticos.

Si dos actividades que tienen un elemento de la IU en común están en una división, mostrar el elemento en ambas actividades resultará redundante y podría generar confusión.

Figura 24: Elementos de la IU duplicados en la división de actividad.

Para saber cuándo las actividades están en una división, consulta el SplitController.splitInfoList o registra un objeto de escucha con SplitControllerCallbackAdapter para los cambios en el estado de la división. Luego, ajusta la IU según corresponda:

Kotlin

val layout = layoutInflater.inflate(R.layout.activity_main, null)
val view = layout.findViewById<View>(R.id.infoButton)
lifecycleScope.launch {
    lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
        splitController.splitInfoList(this@SplitDeviceActivity) // The activity instance.
            .collect { list ->
                view.visibility = if (list.isEmpty()) View.VISIBLE else View.GONE
            }
    }
}

Java

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    . . .
    new SplitControllerCallbackAdapter(SplitController.getInstance(this))
        .addSplitListener(
            this,
            Runnable::run,
            splitInfoList -> {
                View layout = getLayoutInflater().inflate(R.layout.activity_main, null);
                layout.findViewById(R.id.infoButton).setVisibility(
                    splitInfoList.isEmpty() ? View.VISIBLE : View.GONE);
            });
}

Las corrutinas se pueden iniciar en cualquier estado del ciclo de vida, pero, por lo general, se inician el estado STARTED para conservar recursos (consulta Cómo usar corrutinas de Kotlin con optimizados para ciclos de vida para obtener más información).

Las devoluciones de llamada se pueden realizar en cualquier estado del ciclo de vida, incluso cuando se detiene una actividad. Por lo general, los objetos de escucha deben estar registrados en onStart() y no deben registrarse en onStop().

Ventana modal completa

Algunas actividades bloquean a los usuarios para que no interactúen con la aplicación hasta que se realice una acción especificada (por ejemplo, una actividad de la pantalla de acceso, la pantalla de confirmación de la política o un mensaje de error). Se debe evitar que las actividades modales aparezcan en una división.

Una actividad puede verse forzada a llenar siempre la ventana de tareas mediante la configuración de expansión:

<ActivityRule
    window:alwaysExpand="true">
    <ActivityFilter
        window:activityName=".FullWidthActivity"/>
</ActivityRule>

Cómo finalizar actividades

Los usuarios pueden finalizar las actividades a ambos lados de la división deslizando el dedo desde el borde. de la pantalla:

Figura 25. Gesto de deslizar el dedo para finalizar la actividad B.
Figura 26. Gesto de deslizar el dedo para finalizar la actividad A.

Si el dispositivo está configurado a fin de usar el botón Atrás en lugar de la navegación por gestos, la entrada se envía a la actividad enfocada, es decir, aquella que se tocó o se inició por última vez.

Cuando se terminan todas las actividades en un contenedor, el efecto en el contenedor opuesto dependerá de la configuración de la división.

Atributos de configuración

Puedes especificar atributos de reglas de vinculación de divisiones para configurar cómo el hecho de finalizar todas las actividades en un lado de la división afecta las actividades del otro lado. Los atributos son los siguientes:

  • window:finishPrimaryWithSecondary: Indica cómo el hecho de finalizar todas las actividades del contenedor secundario afecta las actividades del contenedor principal.
  • window:finishSecondaryWithPrimary: Indica cómo el hecho de finalizar todas las actividades del contenedor principal afecta las actividades del contenedor secundario.

Entre los valores posibles de los atributos, se incluyen los siguientes:

  • always: Siempre finaliza las actividades en el contenedor asociado.
  • never: Nunca finaliza las actividades en el contenedor asociado.
  • adjacent: Finaliza las actividades en el contenedor asociado cuando ambos contenedores se muestran uno al lado del otro, pero no cuando dos contenedores se apilan

Por ejemplo:

<SplitPairRule
    &lt;!-- Do not finish primary container activities when all secondary container activities finish. --&gt;
    window:finishPrimaryWithSecondary="never"
    &lt;!-- Finish secondary container activities when all primary container activities finish. --&gt;
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Configuración predeterminada

Cuando terminan todas las actividades de un contenedor de una división, el contenedor restante ocupa toda la ventana:

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

División que contiene las actividades A y B. A finalizó, y toda la ventana queda ocupada por B.

División que contiene las actividades A y B. B finalizó y toda la ventana queda ocupada por A.

Cómo finalizar actividades al mismo tiempo

Finaliza automáticamente las actividades en el contenedor principal cuando todas las actividades en el final del contenedor secundario:

<SplitPairRule
    window:finishPrimaryWithSecondary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

División que contiene las actividades A y B. B finalizó, lo que también finalizó A, y la ventana de tareas queda vacía.

División que contiene las actividades A y B. A finalizó, y solo B queda en la ventana de tareas.

Finaliza automáticamente las actividades en el contenedor secundario cuando del contenedor principal finalizan:

<SplitPairRule
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

División que contiene las actividades A y B. A finalizó, lo que también finalizó B, y la ventana de tareas queda vacía.

División que contiene las actividades A y B. B finalizó, y solo A queda en la ventana de tareas.

Debe finalizar las actividades juntas cuando todas las actividades del nivel principal o finaliza el contenedor secundario:

<SplitPairRule
    window:finishPrimaryWithSecondary="always"
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

División que contiene las actividades A y B. A finalizó, lo que también finalizó B, y la ventana de tareas queda vacía.

División que contiene las actividades A y B. B finalizó, lo que también finalizó A, y la ventana de tareas queda vacía.

Cómo finalizar varias actividades en contenedores

Si se apilan varias actividades en un contenedor dividido, finalizar una de las que esté en la parte inferior de la pila no finalizará automáticamente aquellas que se encuentren en la parte superior.

Por ejemplo, si dos actividades están en el contenedor secundario, y C está sobre B:

La pila de actividades secundaria que contiene la actividad C sobre B se apila sobre la principal que contiene la actividad A.

La configuración de la división se define según la configuración de las actividades A y B:

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Cuando termina la actividad superior, se conserva la división.

División con la actividad A en el contenedor principal y las actividades B y C en el secundario, con C apilada sobre B. C finaliza y deja A y B en la división de actividad.

Terminar la actividad inferior (raíz) del contenedor secundario no quita lo siguiente: las actividades que le siguen; por lo que también mantiene la división.

División con la actividad A en el contenedor principal y las actividades B y C en el secundario, con C apilada sobre B. B finaliza y deja A y C en la división de actividad.

También se ejecutan las reglas adicionales para finalizar actividades al mismo tiempo, como finalizar la actividad secundaria con la principal:

<SplitPairRule
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

División con la actividad A en el contenedor principal y las actividades B y C en el secundario, con C apilada sobre B. A finaliza, lo que también finaliza B y C.

Cuando la división se configura para finalizar la actividad principal y la secundaria al mismo tiempo, se ve lo siguiente:

<SplitPairRule
    window:finishPrimaryWithSecondary="always"
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

División con la actividad A en el contenedor principal y las actividades B y C en el secundario, con C apilada sobre B. C finaliza y deja A y B en la división de actividad.

División con la actividad A en el contenedor principal y las actividades B y C en el secundario, con C apilada sobre B. B finaliza y deja A y C en la división de actividad.

División con la actividad A en el contenedor principal y las actividades B y C en el secundario, con C apilada sobre B. A finaliza, lo que también finaliza B y C.

Cómo cambiar las propiedades de las divisiones durante el tiempo de ejecución

No se pueden cambiar las propiedades de una división activa y visible. El cambio en las reglas de división afecta los inicios de actividades adicionales y los contenedores nuevos, pero no las divisiones existentes y activas.

Para cambiar las propiedades de las divisiones activas, finaliza las actividades laterales o presentes en la división y vuelve a iniciarlas al lado con una configuración nueva.

Propiedades de división dinámica

Android 15 (nivel de API 35) y versiones posteriores compatibles con WindowManager 1.4 de Jetpack y versiones posteriores ofrecen funciones dinámicas que permiten la configurabilidad de las divisiones de incorporación de actividades, incluidas las siguientes:

  • Expansión de paneles: Un divisor interactivo y arrastrable permite a los usuarios cambiar el tamaño de los paneles en una presentación dividida.
  • Fijado de actividades: Los usuarios pueden fijar el contenido en un contenedor y aislar la navegación en el contenedor de la navegación en el otro contenedor.
  • Atenuación de diálogo en pantalla completa: al mostrar un diálogo, las apps pueden especificar si atenuar toda la ventana de tareas o solo el contenedor que abrió la .

Expansión del panel

La expansión de paneles permite a los usuarios ajustar la cantidad de espacio de pantalla asignado a las dos actividades en un diseño de dos paneles.

Para personalizar el aspecto del divisor de ventanas y establecer su rango de arrastre, haz lo siguiente:

  1. Crea una instancia de DividerAttributes

  2. Personaliza los atributos del divisor:

    • color: Es el color del separador de panel arrastrable.

    • widthDp: Es el ancho del separador de panel desplazable. Establece WIDTH_SYSTEM_DEFAULT para permitir que el sistema determine el ancho del divisor.

    • Rango de arrastre: Es el porcentaje mínimo de la pantalla que puede ocupar cualquiera de los paneles. Puede variar de 0.33 a 0.66. Establecer en DRAG_RANGE_SYSTEM_DEFAULT para permitir que el sistema determine el arrastre del rango de destino de la ruta.

Kotlin

val splitAttributesBuilder: SplitAttributes.Builder = SplitAttributes.Builder()
    .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
    .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)

if (WindowSdkExtensions.getInstance().extensionVersion >= 6) {
    splitAttributesBuilder.setDividerAttributes(
      DividerAttributes.DraggableDividerAttributes.Builder()
        .setColor(getColor(context, R.color.divider_color))
        .setWidthDp(4)
        .setDragRange(DividerAttributes.DragRange.DRAG_RANGE_SYSTEM_DEFAULT)
        .build()
    )
}
val splitAttributes: SplitAttributes = splitAttributesBuilder.build()

Java

SplitAttributes.Builder splitAttributesBuilder = new SplitAttributes.Builder()
    .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
    .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT);

if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 6) {
    splitAttributesBuilder.setDividerAttributes(
      new DividerAttributes.DraggableDividerAttributes.Builder()
        .setColor(ContextCompat.getColor(context, R.color.divider_color))
        .setWidthDp(4)
        .setDragRange(DividerAttributes.DragRange.DRAG_RANGE_SYSTEM_DEFAULT)
        .build()
    );
}
SplitAttributes splitAttributes = splitAttributesBuilder.build();

Fijación de actividades

La fijación de actividades permite a los usuarios fijar una de las ventanas divididas para que La actividad se mantiene igual mientras los usuarios navegan dentro de la otra ventana. Actividad fijas brinda una experiencia mejorada de multitarea.

Para habilitar el fijado de actividades en tu app, haz lo siguiente:

  1. Agrega un botón al archivo de diseño de la actividad que deseas fijar, por ejemplo, la actividad de detalles de un diseño de lista-detalles:

    <androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:id="@+id/detailActivity"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:background="@color/white"
     tools:context=".DetailActivity">
    
    <TextView
       android:id="@+id/textViewItemDetail"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:textSize="36sp"
       android:textColor="@color/obsidian"
       app:layout_constraintBottom_toTopOf="@id/pinButton"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent" />
    
    <androidx.appcompat.widget.AppCompatButton
       android:id="@+id/pinButton"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="@string/pin_this_activity"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toBottomOf="@id/textViewItemDetail"/>
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    
  2. En el método onCreate() de la actividad, configura un objeto de escucha de clic en la :

    Kotlin

    pinButton = findViewById(R.id.pinButton)
    pinButton.setOnClickListener {
        val splitAttributes: SplitAttributes = SplitAttributes.Builder()
            .setSplitType(SplitAttributes.SplitType.ratio(0.66f))
            .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
            .build()
    
        val pinSplitRule = SplitPinRule.Builder()
            .setSticky(true)
            .setDefaultSplitAttributes(splitAttributes)
            .build()
    
        SplitController.getInstance(applicationContext).pinTopActivityStack(taskId, pinSplitRule)
    }

    Java

    Button pinButton = findViewById(R.id.pinButton);
    pinButton.setOnClickListener( (view) => {
        SplitAttributes splitAttributes = new SplitAttributes.Builder()
            .setSplitType(SplitAttributes.SplitType.ratio(0.66f))
            .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
            .build();
    
        SplitPinRule pinSplitRule = new SplitPinRule.Builder()
            .setSticky(true)
            .setDefaultSplitAttributes(splitAttributes)
            .build();
    
        SplitController.getInstance(getApplicationContext()).pinTopActivityStack(getTaskId(), pinSplitRule);
    });

Atenuación de pantalla completa

Por lo general, las actividades atenúan sus pantallas para llamar la atención a un diálogo. En incorporación de actividades, ambos paneles de la pantalla de panel doble deberían atenuarse, no solo el panel que contiene la actividad que abrió el diálogo, para una IU unificada una experiencia fluida a los desarrolladores.

Con WindowManager 1.4 y versiones posteriores, toda la ventana de la app se atenúa de forma predeterminada cuando se abre un diálogo (consulta EmbeddingConfiguration.DimAreaBehavior.ON_TASK).

Para atenuar solo el contenedor de la actividad que abrió el diálogo, usa EmbeddingConfiguration.DimAreaBehavior.ON_ACTIVITY_STACK.

Cómo extraer una actividad de una división a una ventana completa

Crea una nueva configuración que muestre la ventana completa de la actividad lateral y, luego, reiniciar la actividad con un intent que se resuelva en la misma instancia.

Cómo comprobar la compatibilidad con las divisiones durante el tiempo de ejecución

La incorporación de actividades es compatible con Android 12L (nivel de API 32) y versiones posteriores, pero también está disponible en algunos dispositivos con versiones anteriores de la plataforma. Para comprobar la disponibilidad de la función durante el tiempo de ejecución, usa la propiedad SplitController.splitSupportStatus o el método SplitController.getSplitSupportStatus():

Kotlin

if (SplitController.getInstance(this).splitSupportStatus ==
     SplitController.SplitSupportStatus.SPLIT_AVAILABLE) {
     // Device supports split activity features.
}

Java

if (SplitController.getInstance(this).getSplitSupportStatus() ==
     SplitController.SplitSupportStatus.SPLIT_AVAILABLE) {
     // Device supports split activity features.
}

Si no se admiten las divisiones, las actividades se iniciarán en la parte superior de la pila de actividades (como en el modelo de incorporación sin actividad).

Cómo evitar la anulación del sistema

Los fabricantes de dispositivos Android (fabricantes de equipos originales o OEMs) pueden implementar la incorporación de actividades como una función del sistema del dispositivo. El sistema especifica las reglas de división para las apps de varias actividades, lo que anula el comportamiento del sistema de ventanas de las apps. La anulación del sistema fuerza las apps de varias actividades a un modo de incorporación de actividad definido por el sistema.

La incorporación de actividades del sistema puede mejorar la presentación de la app mediante la función multipanel Diseños, como lista-detalles, sin ningún cambio en la app. Sin embargo, el la incorporación de actividades del sistema también puede provocar diseños incorrectos de la app, errores o conflictos con la incorporación de actividades implementada por la app

Para evitar o permitir la incorporación de actividades del sistema, configura una propiedad en el archivo de manifiesto de la app, por ejemplo:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application>
        <property
            android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE"
            android:value="true|false" />
    </application>
</manifest>

El nombre de la propiedad se define en el objeto WindowProperties de Jetpack WindowManager. Establece el valor en false si tu app implementa la incorporación de actividades. si quieres impedir que el sistema aplique su incorporación de actividades reglas de firewall a tu app. Establece el valor en true para permitir que el sistema aplique de la actividad definida por el sistema.

Limitaciones, restricciones y advertencias

  • Solo la app host de la tarea, que se identifica como propietaria de la actividad raíz de esta, puede organizar e incorporar otras actividades en la tarea. Si las actividades que admiten incorporación y divisiones se ejecutan en una tarea que pertenece a una aplicación diferente, la incorporación y las divisiones no funcionarán para esas actividades.
  • Las actividades solo se pueden organizar dentro de una misma tarea. Iniciar una actividad en una tarea nueva siempre la coloca en una ventana expandida nueva fuera de cualquier división existente.
  • Solo se pueden organizar y dividir las actividades que formen parte de un mismo proceso. La devolución de llamada SplitInfo solo informa actividades que pertenecen al mismo proceso, ya que no hay manera de conocer aquellas que existan en diferentes procesos.
  • Cada regla de actividad individual o de vinculación se aplica solo a los inicios de actividad que ocurran después de que se registre la regla. Por el momento, no hay forma de actualizar las divisiones existentes ni sus propiedades visuales.
  • La configuración del filtro de vinculación de divisiones debe coincidir con los intents que se usan cuando se inician actividades por completo. La coincidencia se produce cuando se inicia una actividad nueva desde el proceso de la aplicación, por lo que es posible que no conozcas los nombres de los componentes que se resuelven más adelante en el proceso del sistema cuando se usan intents implícitos. Si no se conoce el nombre de un componente en el momento del lanzamiento, se en su lugar ("*/*") y filtrar según en la acción de intent.
  • Por el momento, no hay forma de mover las actividades entre contenedores ni dentro o fuera de las divisiones después de crearlas. La biblioteca WindowManager solo crea las divisiones cuando se inician actividades nuevas con reglas que coincidan, y las divisiones se destruyen cuando finaliza la última actividad de un contenedor de divisiones.
  • Las actividades pueden volver a iniciarse cuando cambia la configuración, de modo que, cuando se crea o quita una división y cambian los límites de la actividad, esta puede pasar por la destrucción completa de la instancia anterior y la creación de la nueva. Como resultado, los desarrolladores de apps deberían tener cuidado con cuestiones como el inicio de actividades nuevas a partir de devoluciones de llamada de ciclo de vida.
  • Los dispositivos deben incluir la interfaz de extensiones de ventana para admitir la actividad. a través de la incorporación de texto. Casi todos los dispositivos de pantalla grande que ejecutan Android 12L (nivel de API) 32) o versiones posteriores incluyen la interfaz. Sin embargo, algunos dispositivos de pantalla grande que no pueden ejecutar varias actividades no incluyen la interfaz de extensiones de ventana. Si un dispositivo de pantalla grande no admite el modo multiventana, es posible que no admita la incorporación de actividades.

Recursos adicionales