Personaliza la transición de elementos compartidos

Para personalizar cómo se ejecuta la animación de transición de elementos compartidos, hay algunos parámetros que se pueden usar para cambiar cómo realiza la transición de los elementos compartidos.

Especificaciones de animación

Si quieres cambiar las especificaciones de la animación que se usan para el tamaño y el movimiento de posición, puedes especificar un parámetro boundsTransform diferente en Modifier.sharedElement(). Esto proporciona la posición inicial de Rect y la posición Rect de destino.

Por ejemplo, para hacer que el texto del ejemplo anterior se mueva con un movimiento de arco, especifica el parámetro boundsTransform para usar una especificación de keyframes:

val textBoundsTransform = BoundsTransform { initialBounds, targetBounds ->
    keyframes {
        durationMillis = boundsAnimationDurationMillis
        initialBounds at 0 using ArcMode.ArcBelow using FastOutSlowInEasing
        targetBounds at boundsAnimationDurationMillis
    }
}
Text(
    "Cupcake", fontSize = 28.sp,
    modifier = Modifier.sharedBounds(
        rememberSharedContentState(key = "title"),
        animatedVisibilityScope = animatedVisibilityScope,
        boundsTransform = textBoundsTransform
    )
)

Puedes usar cualquier AnimationSpec. En este ejemplo, se usa una especificación keyframes.

Figura 1: Ejemplo que muestra diferentes parámetros de boundsTransform

Modo de cambio de tamaño

Cuando realizas animaciones entre dos límites compartidos, puedes establecer el parámetro resizeMode en RemeasureToBounds o ScaleToBounds. Este parámetro determina cómo el elemento compartido realiza la transición entre los dos estados. ScaleToBounds primero mide el diseño secundario con las restricciones de vista previa (o destino). Luego, se escala el diseño estable del elemento secundario para que se ajuste a los límites compartidos. Se puede considerar ScaleToBounds como una "escala gráfica" entre los estados.

En cambio, RemeasureToBounds vuelve a medir y rediseñar el diseño secundario de sharedBounds con restricciones fijas animadas basadas en el tamaño de destino. La nueva medición se activa por el cambio de tamaño de los límites, que podría ser cada fotograma.

Para los elementos componibles Text, se recomienda usar ScaleToBounds, ya que evitará el rediseño y el reprocesamiento del texto en diferentes líneas. Para límites que son diferentes relaciones de aspecto y si deseas una continuidad fluida entre los dos elementos compartidos, se recomienda RemeasureToBounds.

La diferencia entre los dos modos de cambio de tamaño se puede ver en los siguientes ejemplos:

ScaleToBounds

RemeasureToBounds

Ir al diseño final

De forma predeterminada, cuando haces la transición entre dos diseños, el tamaño del diseño se anima entre su estado inicial y final. Este puede ser un comportamiento no deseado cuando se anima contenido, como texto.

En el siguiente ejemplo, se ilustra el texto de descripción "Lorem Ipsum" que ingresa a la pantalla de dos maneras diferentes. En el primer ejemplo, el texto se reprocesa a medida que ingresa a medida que aumenta el tamaño del contenedor, mientras que, en el segundo, no se reprocesa a medida que crece. Agregar Modifier.skipToLookaheadSize() evita el reprocesamiento a medida que crece.

No hay Modifier.skipToLookahead(). Observa el reprocesamiento del texto de "Lorem Ipsum".

Modifier.skipToLookahead(): Observa que el texto "Lorem Ipsum" mantiene su estado final al inicio de la animación.

Recortar y superposiciones

Un concepto importante a la hora de crear elementos compartidos en Compose es que, para que se compartan entre diferentes elementos componibles, la renderización del elemento componible se eleva a una superposición de capas cuando se inicia la transición para que coincida en el destino. El efecto de esto es que omitirá los límites del elemento superior y las transformaciones de sus capas (por ejemplo, alfa y escala).

Se renderizará sobre otros elementos de la IU no compartidos. Una vez que finalice la transición, el elemento se descartará de la superposición a su propio DrawScope.

Para recortar un elemento compartido a una forma, usa la función Modifier.clip() estándar. Colócalo después de sharedElement():

Image(
    painter = painterResource(id = R.drawable.cupcake),
    contentDescription = "Cupcake",
    modifier = Modifier
        .size(100.dp)
        .sharedElement(
            rememberSharedContentState(key = "image"),
            animatedVisibilityScope = this@AnimatedContent
        )
        .clip(RoundedCornerShape(16.dp)),
    contentScale = ContentScale.Crop
)

Si necesitas asegurarte de que un elemento compartido nunca se renderice fuera de un contenedor superior, puedes configurar clipInOverlayDuringTransition en sharedElement(). De forma predeterminada, para los límites compartidos anidados, clipInOverlayDuringTransition usa la ruta de acceso de recorte del sharedBounds() superior.

Para admitir elementos específicos de la IU, como una barra inferior o un botón de acción flotante, siempre en la parte superior durante una transición de elementos compartidos, usa Modifier.renderInSharedTransitionScopeOverlay(). De forma predeterminada, este modificador mantiene el contenido en la superposición durante el tiempo en el que la transición compartida está activa.

Por ejemplo, en Jetsnack, se debe colocar BottomAppBar sobre el elemento compartido hasta que la pantalla no sea visible. Agregar el modificador al elemento componible lo mantiene elevado.

Sin Modifier.renderInSharedTransitionScopeOverlay()

Con Modifier.renderInSharedTransitionScopeOverlay()

En ocasiones, es posible que quieras que el elemento componible no compartido se anime y permanezca encima de los otros elementos componibles antes de la transición. En esos casos, usa renderInSharedTransitionScopeOverlay().animateEnterExit() para animar el elemento componible a medida que se ejecuta la transición de elementos compartidos:

JetsnackBottomBar(
    modifier = Modifier
        .renderInSharedTransitionScopeOverlay(
            zIndexInOverlay = 1f,
        )
        .animateEnterExit(
            enter = fadeIn() + slideInVertically {
                it
            },
            exit = fadeOut() + slideOutVertically {
                it
            }
        )
)

Figura 2: La barra inferior de la app se desliza hacia adentro y afuera a medida que pasa la animación

En el caso excepcional de que desees que tu elemento compartido no se renderice en una superposición, puedes configurar renderInOverlayDuringTransition en sharedElement() como falso.

Notificar a los diseños del mismo nivel sobre los cambios en el tamaño de los elementos compartidos

De forma predeterminada, sharedBounds() y sharedElement() no notifican al contenedor superior sobre ningún cambio de tamaño durante la transición de diseño.

Para propagar los cambios de tamaño al contenedor superior a medida que realiza la transición, cambia el parámetro placeHolderSize a PlaceHolderSize.animatedSize. Si lo haces, el elemento aumentará o se reducirá. Todos los demás elementos del diseño responden al cambio.

PlaceholderSize.contentSize (predeterminado)

PlaceholderSize.animatedSize

(Observa cómo los otros elementos de la lista se mueven hacia abajo en respuesta al crecimiento de un elemento)