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.
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 cómo la app muestra sus actividades, una al lado de la otra, o apiladas, mediante la creación de un archivo de configuración XML o 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 con 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, y este se apila sobre el principal en pantallas pequeñas, por lo que la pila de actividades y la navegación hacia atrás son coherentes con el orden de las actividades ya integradas en tu app.
La incorporación de actividades te permite mostrarlas de varias formas. Tu app puede dividir la ventana de tareas iniciando dos actividades, una al lado de la otra y de manera simultánea:
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:
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:
Al costado y desplazando la división hacia un lado, ocultando la actividad principal anterior:
Iniciando una actividad en el lugar, sobre la otra, es decir, en la misma pila de actividades:
Iniciando una ventana de actividad completa en la misma tarea:
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 primer plano, y se alinea con el comportamiento de la navegación con botones.
Diseño multipanel
Jetpack WindowManager te permite compilar un diseño que incorpore actividad de varios paneles en dispositivos con pantalla grande con Android 12L (nivel de API 32) o versiones posteriores, y en algunos dispositivos con versiones de plataforma anteriores. 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 presentación de alta calidad, el sistema inicia la actividad de la lista y, luego, la aplicación inicia de inmediato la actividad de los detalles. El sistema de transición espera hasta que se dibujen ambas actividades y, luego, las muestra juntas. Para el usuario, las dos actividades se inician como una sola.
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 derechartl
: De derecha a izquierdalocale
: Se determinaltr
ortl
a partir de la configuración regional
Consulta la sección Configuración de XML para ver ejemplos.
Para las reglas creadas con las APIs de WindowManager, crea un objeto SplitAttributes
con SplitAttributes.Builder
y llama a los siguientes métodos de compilador:
setSplitType()
: Establece las proporciones de los contenedores divididos. ConsultaSplitAttributes.SplitType
para ver los argumentos válidos, incluido el métodoSplitAttributes.SplitType.ratio()
.setLayoutDirection()
: Establece el diseño de los contenedores. ConsultaSplitAttributes.LayoutDirection
para ver los valores posibles.
Consulta la sección API de WindowManager para ver ejemplos.
Marcadores de posición
Las actividades de marcadores de posición son actividades secundarias vacías que ocupan un área de una división de actividad. En definitiva, se las reemplazará por otra actividad que incluya contenido. Por ejemplo, una actividad de marcador de posición puede ocupar el lado secundario de una división de actividad en un diseño de lista-detalles hasta que se seleccione un elemento de la lista. En ese momento, una actividad que contiene la información de los detalles 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 dividir actividades. Los marcadores de posición terminan automáticamente cuando el tamaño de la pantalla 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.
Sin embargo, el atributo stickyPlaceholder
de un método SplitPlaceholderRule
o setSticky()
de SplitPlaceholder.Builder
puede anular el 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).
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:
Esto da como resultado el siguiente orden Z de las actividades en la misma tarea:
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:
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.
El resultado es el siguiente orden Z de las actividades en la misma tarea:
En una ventana de tareas más pequeña, la aplicación se reduce a una sola actividad, con C en la parte superior:
Orientación vertical fija
La configuración del manifiesto android:screenOrientation permite a las apps restringir las actividades 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.
De manera similar, cuando se habilita la incorporación de actividades, los OEMs pueden personalizar los dispositivos para que realicen actividades con formato letterbox y formato vertical fijo en orientación horizontal en pantallas grandes (ancho superior o igual a 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.
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 aplican el formato letterbox a las actividades con orientación vertical fija.
Configuración de divisiones
Las divisiones de actividad se configuran con las reglas de división. Puedes definir las reglas de división en un archivo de configuración XML o mediante llamadas a la API de WindowManager de Jetpack.
En cualquier caso, tu app debe acceder a la biblioteca de WindowManager y debe informar al sistema que la app implementó la incorporación de actividades.
Haz lo siguiente:
Agrega la dependencia más reciente de la biblioteca de WindowManager al archivo
build.gradle
de nivel de módulo de tu app, 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.
Informa al sistema que tu app implementó la incorporación de actividades.
Agrega la propiedad
android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED
al elemento <application> del archivo de manifiesto de la app y establece el valor en "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, se inhabilitaron las divisiones de incorporación de actividades, a menos que se agregue la propiedad al manifiesto y se configure como "true".
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:
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>
Crear un inicializador.
El componente
RuleController
de WindowManager analiza el archivo de configuración XML y pone las reglas a disposición del sistema. Una biblioteca de Startup de JetpackInitializer
pone el archivo XML a disposición deRuleController
en el inicio de la app para que se apliquen las reglas cuando se inicie una actividad.Para crear un inicializador, haz lo siguiente:
Agrega la dependencia más reciente de la biblioteca de Jetpack Startup al archivo
build.gradle
de nivel de módulo, por ejemplo:implementation 'androidx.startup:startup-runtime:1.1.1'
Crea una clase que implemente la interfaz
Initializer
.El inicializador hace que las reglas de división estén disponibles para
RuleController
pasando el ID del archivo de configuración XML (main_split_config.xml
) al métodoRuleController.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(); } }
Crea un proveedor de contenido para las definiciones de la regla.
Agrega
androidx.startup.InitializationProvider
al archivo de manifiesto de tu app como un<provider>
. Incluye una referencia a la implementación de tu inicializador deRuleController
,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 y luego inicializaSplitInitializer
antes de que se llame al métodoonCreate()
de la app. Como resultado, las reglas de división están vigentes cuando comienza la actividad principal de la app.
API de WindowManager
Puedes implementar la incorporación de actividades de manera programática con unas pocas llamadas a la API. 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:
Crea una regla de división:
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 );
Agrega el filtro a un conjunto de filtros:
Kotlin
val filterSet = setOf(splitPairFilter)
Java
Set<SplitPairFilter> filterSet = new HashSet<>(); filterSet.add(splitPairFilter);
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 asigna el área de visualización disponible 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 del área de visualización disponible.setLayoutDirection()
: Especifica cómo se distribuyen los contenedores de actividades uno respecto del otro (el contenedor principal se distribuye primero).
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 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 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. 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, lo que da como resultado actividades que ocupan toda la ventana de tareas en orientación vertical en la mayoría de las tablets. Consulta tambiénSPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT
ysetMaxAspectRatioInLandscape()
. El valor predeterminado para la orientación horizontal esALWAYS_ALLOW
.setFinishPrimaryWithSecondary()
: Establece cómo el hecho de finalizar todas las actividades del contenedor secundario afecta las actividades del 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 del contenedor secundario cuando terminan todas las actividades 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 valorfalse
especifica que las actividades nuevas se apilan sobre las que ya se encuentran en el contenedor secundario.
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);
Crea un marcador de posición para el contenedor secundario cuando el contenido no esté disponible:
Crea un
ActivityFilter
que identifique la actividad con la que el marcador de posición comparte la 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 );
Agrega el filtro a un conjunto de filtros:
Kotlin
val placeholderActivityFilterSet = setOf(placeholderActivityFilter)
Java
Set<ActivityFilter> placeholderActivityFilterSet = new HashSet<>(); placeholderActivityFilterSet.add(placeholderActivityFilter);
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 determinan cuándo aplicar la regla mediante la identificación de actividades con las que se asocia la actividad del marcador de posición.Intent
: Especifica el inicio de la actividad del marcador de posició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 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 valor predeterminado es 1.4, lo que da como resultado actividades que ocupan toda la ventana de tareas en orientación vertical en la mayoría de las tablets. Consulta tambiénSPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT
ysetMaxAspectRatioInLandscape()
. El valor predeterminado para la orientación horizontal esALWAYS_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 aparecerá sobre la pila de actividades en pantallas pequeñas una vez que este aparezca por primera vez en una división con suficiente ancho mínimo.
Agrega la regla a
RuleController
de WindowManager:Kotlin
ruleController.addRule(splitPlaceholderRule)
Java
ruleController.addRule(splitPlaceholderRule);
Especifica las actividades que nunca deben ser parte de una división:
Crea un
ActivityFilter
que identifique una actividad que siempre debe ocupar toda el área de visualización de la tarea:Kotlin
val expandedActivityFilter = ActivityFilter( ComponentName(this, ExpandedActivity::class.java), null )
Java
ActivityFilter expandedActivityFilter = new ActivityFilter( new ComponentName(this, ExpandedActivity.class), null );
Agrega el filtro a un conjunto de filtros:
Kotlin
val expandedActivityFilterSet = setOf(expandedActivityFilter)
Java
Set<ActivityFilter> expandedActivityFilterSet = new HashSet<>(); expandedActivityFilterSet.add(expandedActivityFilter);
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.
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:
Modelo de confianza
Los procesos de host que incorporan actividades de otras apps pueden redefinir la presentación de las actividades incorporadas, incluidos el tamaño, la posición, el recorte y la transparencia. Los hosts maliciosos pueden usar esta función para engañar a los usuarios y crear una captura de clic u otros ataques de compensación de IU.
Para evitar el uso inadecuado de la incorporación de actividades entre apps, Android requiere que las apps acepten habilitar la incorporación de sus actividades. Las apps pueden designar hosts como confiables o no 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 y Autenticación de tu 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 los certificados del host, usa la biblioteca androidx.security.app.authenticator
para la autenticación. Si el host hace la autenticación después de incorporar tu actividad, puedes mostrar el contenido real. De lo contrario, puedes informarle al usuario que no se permitió la acción 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 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>
, se deben aplicar android:knownActivityEmbeddingCerts
o 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 implementan la incorporación de actividades en una sola app. Los objetos SplitPairRule
y SplitPairFilter
o ActivityRule
y ActivityFilter
especifican actividades incorporadas y divisiones de ventanas 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 de host deben saber si las actividades de destino permiten la incorporación entre apps.
Si una actividad incorporada inicia una nueva actividad en la misma tarea y la nueva actividad no 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 que las actividades se inicien en la misma tarea.
Ejemplos de divisiones
División desde la ventana completa
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 no esté disponible para el contenedor secundario de la división hasta que el usuario interactúe con la actividad en el contenedor principal (por ejemplo, cuando el usuario seleccione un elemento de un menú de navegación). Una actividad de marcador de posición puede llenar el vacío hasta que el contenido se pueda mostrar en el contenedor secundario de la división (consulta la sección Marcadores 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>
División de vínculo directo
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 toda la ventana de tareas. En dispositivos más grandes, se muestra junto a la lista.
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:
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 necesite ir a una sección de detalles secundarios, pero mantener la actividad principal en su lugar:
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:
El apilado de actividades unas sobre otras es el comportamiento predeterminado cuando estas se inician desde una que se encuentra en el mismo contenedor secundario. Las actividades iniciadas desde el contenedor principal dentro de una división activa también terminan en el 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.
Reemplazo de actividades
Las actividades se pueden reemplazar en la pila del contenedor secundario (por ejemplo, cuando se usa la actividad principal 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í.
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 navegue hacia atrás desde B, aparecerá 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 sobre una división existente es colocar los nuevos contenedores secundarios en la parte superior y conservar los antiguos 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 actividad principal (el menú), envía nuevos intents que se resuelvan en la misma instancia, pero que activen un estado o una 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.
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.
Para crear una nueva división, inicia la actividad nueva al lado del contenedor secundario existente. Declara las configuraciones para las divisiones A/B y B/C, e inicia 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).
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.
Para saber cuándo las actividades están en una división, verifica el flujo de SplitController.splitInfoList
o registra un objeto de escucha con SplitControllerCallbackAdapter
para ver 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 en el estado STARTED
para conservar recursos (consulta Cómo usar corrutinas de Kotlin con componentes 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 en cualquiera de los lados de la división si deslizan el dedo desde el borde de la pantalla:
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 están apilados.
Por ejemplo:
<SplitPairRule
<!-- Do not finish primary container activities when all secondary container activities finish. -->
window:finishPrimaryWithSecondary="never"
<!-- Finish secondary container activities when all primary container activities finish. -->
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>
Cómo finalizar actividades al mismo tiempo
Finaliza automáticamente las actividades en el contenedor principal cuando finalicen todas las actividades en el contenedor secundario:
<SplitPairRule
window:finishPrimaryWithSecondary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
Finaliza automáticamente las actividades en el contenedor secundario cuando finalicen todas las actividades en el contenedor principal:
<SplitPairRule
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
Finaliza las actividades juntas cuando finalicen todas las actividades del contenedor principal o secundario:
<SplitPairRule
window:finishPrimaryWithSecondary="always"
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
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 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.
Finalizar la actividad inferior (raíz) del contenedor secundario no quita las actividades sobre ella. De esta manera, se conserva la división.
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>
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>
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ámicas
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 la pila 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 pantalla completa del diálogo: Cuando se muestra un diálogo, las apps pueden especificar si se atenúa toda la ventana de tareas o solo el contenedor que abrió el diálogo.
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 panel doble.
Para personalizar el aspecto del divisor de ventanas y establecer su rango de arrastre, haz lo siguiente:
Crea una instancia de
DividerAttributes
Personaliza los atributos del divisor:
color
: Es el color del separador de paneles arrastrable.widthDp
: Es el ancho del separador de paneles que se puede arrastrar. Establece el valor enWIDTH_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. Establece el valor en
DRAG_RANGE_SYSTEM_DEFAULT
para permitir que el sistema determine el rango de arrastre.
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 la pila de actividades
La fijación de la pila de actividades permite a los usuarios fijar una de las ventanas divididas para que la actividad permanezca como está mientras los usuarios navegan en la otra. La fijación de pilas de actividades proporciona una experiencia de multitarea mejorada.
Para habilitar el anclaje de pila de actividades en tu app, haz lo siguiente:
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>
En el método
onCreate()
de la actividad, establece un objeto de escucha onclick en el botón: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 del diálogo de pantalla completa
Por lo general, las actividades atenúan sus pantallas para llamar la atención a un diálogo. En la incorporación de actividades, se deben atenuar ambos paneles de la pantalla de dos paneles, no solo el panel que contiene la actividad que abrió el diálogo, para obtener una experiencia de IU unificada.
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, reinicia 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 la actividad del sistema puede mejorar la presentación de la app a través de diseños de varios paneles, como list-detail, sin ningún cambio en la app. Sin embargo, la incorporación de la actividad del sistema también puede causar errores de diseño, otros errores o conflictos con la incorporación de actividades que implementa 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 o si quieres que el sistema no aplique sus reglas de incorporación de actividades a tu app. Establece el valor en true
para permitir que el sistema aplique la incorporación de actividad definida por el sistema a tu app.
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 al momento del inicio, se puede usar un comodín ("*/*") y realizar el filtrado según 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 incorporación de actividades. Casi todos los dispositivos de pantalla grande con 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
- Codelabs:
- Ruta de aprendizaje: Incorporación de actividades
- App de ejemplo: activity-embedding