Transición de elementos compartidos con React Native

En esta publicación, hablaré sobre cómo lograr la Transición de elementos compartidos con React Native para iOS y Android.

He publicado el código en GitHub y puedes echarle un vistazo si quieres saltar directamente a él.

Intención

Echemos un vistazo a lo que vamos a construir. A continuación se muestra un ejemplo de cuadrícula de fotos donde agregaremos una transición de elemento compartido. De esta manera, podemos hacer una transición sin problemas entre una cuadrícula y la página de detalles de una foto que seleccionamos.

Esta es una experiencia mucho más fluida y continua.

Enfoque

Antes de construir esto, déjame decirte cómo funciona el sistema bajo el capó. Como React Native no admite elementos compartidos verdaderos, cuando decimos que estamos haciendo una transición de elementos compartidos entre dos pantallas, técnicamente no estamos compartiendo ningún elemento. En cambio, cada pantalla tiene su propio elemento individual.

Lo que estoy haciendo es pasar la información sobre el elemento compartido, como su posición y tamaño, entre estos dos elementos.

Cuando se inicia la pantalla de detalles, su fondo se establece en transparente y oculta todos sus elementos. Luego altera los atributos del elemento compartido al que pasó, luego lo hace visible. Luego se anima a su posición natural. A medida que avanza la transición, el fondo de la ventana y el resto de elementos no compartidos se desvanecen lentamente hasta quedar totalmente opacos.

Entonces, aunque el elemento no se comparte técnicamente, este ingenioso truco de humo y espejo hace que parezca que sí.

Entonces, ahora que entendemos cómo funciona este proceso, vamos paso a paso para comprender cómo se ha compartido el elemento simulado y cómo podemos controlar las animaciones.

Paso 1: animación de entrada y salida

Tengo dos pantallas aquí: Cuadrícula y Detalles. Desde la pantalla Cuadrícula, podemos iniciar la pantalla Detalle haciendo clic en una de las imágenes en la cuadrícula. Luego podemos volver a la pantalla de cuadrícula presionando el botón Atrás.

Cuando pasamos de la pantalla de cuadrícula a la pantalla de detalle, tenemos la oportunidad de ejecutar dos conjuntos de animaciones de transición: la transición de salida para la pantalla de cuadrícula y la transición de entrada para la pantalla de detalle.

Veamos cómo implementamos esto.

Sin ninguna transición, así es como se ve la aplicación. Al hacer clic en la imagen individual te lleva a una pantalla detallada.

Agreguemos una transición de salida a la primera pantalla de cuadrícula. Aquí usamos una transición de desvanecimiento simple usando la API animada, que interpola el atributo de opacidad del contenedor de la pantalla de cuadrícula de 1 a 0.

Ahora que lo hemos hecho, así es como se ve:

No está mal. Vemos que la cuadrícula se desvanece a medida que avanzamos a la pantalla de detalles.

Ahora agreguemos otra transición al contenido de la pantalla de detalles a medida que ingresa. Deslicemos el texto en el lugar desde la parte inferior.

Esto se realiza mediante la asignación de un valor animado interpolado a la propiedad translateY del contenedor de texto.

Y así es como se ve:

Bueno, el título y la descripción se deslizan muy bien, pero la imagen aparece abruptamente. Esto se debe a que nuestra transición no apunta específicamente a la imagen. Arreglaremos esto en breve.

Paso 2: capa de transición para el elemento compartido

Ahora agregamos una capa de transición que aparece durante la transición y contiene solo el elemento compartido.

Esta capa se activa cuando se hace clic en la imagen de la cuadrícula. Recibe información sobre el elemento compartido, como su posición y tamaño, tanto de la pantalla Cuadrícula como de la pantalla Detalles.

Paso 3: animación en la capa de transición

Tenemos la información en la capa de transición sobre la posición de origen y destino del elemento compartido. Solo necesitamos animarlos.

Primero configuremos el elemento en función de la posición y el tamaño de origen, luego animémoslo a la ubicación de destino. Esto se puede hacer de dos formas. Echemos un vistazo a los dos.

Al interpolar en el ancho, alto, superior e izquierdo

Este es un enfoque directo. Si queremos que un elemento cambie de un tamaño a otro, y de una posición a otra, modificamos las propiedades de estilo de ancho, alto, superior e izquierdo del elemento.

Y así es como se ve:

Análisis de rendimiento

Cuando usamos Animated, declaramos un gráfico de nodos que representan las animaciones que queremos realizar, y luego usamos un controlador para actualizar un valor Animated usando una curva predefinida.

Aquí hay un desglose de los pasos para una animación y dónde sucede:

https://facebook.github.io/react-native/blog/2017/02/14/using-native-driver-for-animated.html
  • JavaScript: el controlador de animación utiliza requestAnimationFrame para ejecutarse en cada cuadro y actualizar el valor que maneja utilizando el nuevo valor que calcula en función de la curva de animación.
  • JavaScript: los valores intermedios se calculan y pasan a un nodo de accesorios adjunto a una vista.
  • JavaScript: la vista se actualiza mediante setNativeProps.
  • JavaScript al puente nativo.
  • Nativo: se actualiza UIView o android.View.

Como puede ver, la mayor parte del trabajo ocurre en el hilo de JavaScript. Si está bloqueado, la animación omitirá fotogramas. También debe pasar por el puente JavaScript to Native en cada cuadro para actualizar las vistas nativas.

Este problema se puede resolver utilizando useNativeDriver. Esto mueve todos estos pasos a nativo.

Dado que Animated produce un gráfico de nodos animados, se puede serializar y enviar a nativo solo una vez cuando comienza la animación. Esto elimina la necesidad de devolver la llamada al hilo de JavaScript. El código nativo puede encargarse de actualizar las vistas directamente en el hilo de la interfaz de usuario en cada cuadro.

La principal limitación es que solo podemos animar propiedades que no sean de diseño. Funcionarán cosas como la transformación y la opacidad, pero las propiedades de flexbox y posición como la utilizada anteriormente no funcionarán.

Interpolar en transformar y usar useNativeDriver

Animémonos ahora usando transform. Esto requerirá algunas matemáticas para calcular la escala, la posición x e y.

Con esta implementación, si estamos escalando de una imagen más pequeña a una más grande, la imagen se pixelará. Así que renderizaremos la imagen más grande, luego la escalaremos a su tamaño inicial, luego la animaremos al tamaño natural.

Podemos obtener el valor de escala inicial con una línea de JavaScript como esta:

openingScale = sourceDimension.width / destinationDimension.width;

Verá que la imagen a escala y la imagen original no tienen el mismo aspecto, ya que la relación de aspecto de la imagen de origen y la imagen de destino son diferentes, por lo que para resolverla representaremos la imagen con la relación de aspecto de origen en función de la dimensión de destino.

const sourceAspectRatio = source.width / source.height;
const destAspectRatio = destination.width / destination.height;
if (aspectRatio - destAspectRatio> 0) {
  // Imagen del paisaje
  const newWidth = aspectRatio * destination.height;
  openingScale = source.width / newWidth;
} más {
  // imagen vertical
  const newHeight = destination.width / aspectRatio;
  openingScale = source.height / newHeight;
}

Ahora que la escala es correcta, necesitamos obtener la nueva posición basada en la imagen de destino. Esto se puede calcular por la posición de destino menos la mitad de la diferencia entre la dimensión anterior y la nueva dimensión. Lo que equivaldría a:

if (aspectRatio - destAspectRatio> 0) {
  // Imagen del paisaje
  destination.pageX - = (newWidth - destinationWidth) / 2;
} más {
  // imagen vertical
  destination.pageY - = (newHeight - destinationHeight) / 2;
}

¡Eso es perfecto! Ahora tenemos la dimensión correcta y la posición para la imagen en transición.

Ahora necesitamos calcular la posición de traducción desde la cual animar la imagen. Escalaremos la imagen desde el centro, por lo que debemos aplicar nuestra traducción teniendo en cuenta que solo estamos moviendo el centro de la foto. Así que haremos algunos cálculos matemáticos relativamente fáciles, tomando la posición fuente más la mitad de la dimensión fuente. Esto equivaldría a esto:

const translateInitX = source.pageX + source.width / 2;
const translateInitY = source.pageY + source.height / 2;
const translateDestX = destination.pageX + destination.width / 2;
const translateDestY = destination.pageY + destination.height / 2;

Ahora podemos calcular la posición de traslación por la diferencia entre el centro de la imagen de origen y la imagen de destino.

const openingInitTranslateX = translateInitX - translateDestX;
const openingInitTranslateY = translateInitY - translateDestY;

Con esta escala de inicio encontrada y valores de traducción podemos animar usando la API animada.

Eso es. Ahora tenemos la transición funcionando. Ahora podemos usar useNativeDriver ya que ahora solo estamos animando propiedades que no sean de diseño.

Paso 4: Ocultar la imagen de origen y destino durante la transición

En el gif anterior, vimos que durante la transición, la imagen en la que se hizo clic todavía estaba en la misma posición, y la imagen de destino apareció antes de que se completara la transición.

Ocultemos la imagen de origen y de destino durante la transición, para que parezca que la imagen en la que se hizo clic es la que anima a la pantalla de detalles.

Ahora veamos la salida.

Paso 5: maneja el botón Atrás

Durante la transición a la pantalla de detalles usando Animated.timing () cambiamos el AnimatedValue de 0 a 1. Entonces, cuando se hace clic en el botón Atrás, solo tenemos que cambiar el AnimatedValue de 1 a o.

Eso es. Puede consultar el código en Github y probar la demostración en Expo.

Consulte también la transmisión de Eric Vicenti sobre la transición de elementos compartidos.

Gracias por tomarse el tiempo y leer esta publicación. Si esto te parece útil, aplaude y compártelo. Puedes conectarte conmigo en Twitter @narendra_shetty.