Uso del controlador nativo para Animated
Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →
Durante el último año, hemos trabajado en mejorar el rendimiento de las animaciones que utilizan la biblioteca Animated. Las animaciones son muy importantes para crear una experiencia de usuario atractiva, pero también pueden ser difíciles de implementar correctamente. Queremos facilitar a los desarrolladores la creación de animaciones de alto rendimiento sin que tengan que preocuparse por que parte de su código cause retrasos.
¿Qué es esto?
La API Animated se diseñó con una restricción muy importante en mente: es serializable. Esto significa que podemos enviar toda la información sobre la animación al entorno nativo antes de que comience, lo que permite que el código nativo ejecute la animación en el hilo de la interfaz de usuario sin tener que pasar por el puente en cada fotograma. Es muy útil porque una vez que la animación ha comenzado, el hilo de JavaScript puede bloquearse y la animación seguirá ejecutándose sin problemas. En la práctica, esto puede ocurrir con frecuencia porque el código del usuario se ejecuta en el hilo de JavaScript y los renders de React también pueden bloquear JS durante mucho tiempo.
Un poco de historia...
Este proyecto comenzó hace aproximadamente un año, cuando Expo construyó la aplicación li.st en Android. Contrataron a Krzysztof Magiera para construir la implementación inicial en Android. Terminó funcionando bien y li.st fue la primera aplicación en implementar animaciones nativas usando Animated. Unos meses después, Brandon Withrow construyó la implementación inicial en iOS. Luego, Ryan Gomba y yo trabajamos en agregar funciones faltantes como soporte para Animated.event, además de corregir errores que encontramos al usarlo en aplicaciones de producción. Este fue verdaderamente un esfuerzo comunitario y me gustaría agradecer a todos los involucrados, así como a Expo por patrocinar gran parte del desarrollo. Ahora se utiliza en los componentes Touchable de React Native, así como para animaciones de navegación en la biblioteca recién lanzada React Navigation.
¿Cómo funciona?
Primero, veamos cómo funcionan actualmente las animaciones usando Animated con el controlador JS. Al usar Animated, declaras un grafo de nodos que representan las animaciones que deseas realizar, y luego usas un controlador para actualizar un valor Animated usando una curva predefinida. También puedes actualizar un valor Animated conectándolo a un evento de una View mediante Animated.event.

Aquí hay un desglose de los pasos para una animación y dónde ocurren:
-
JS: El controlador de animación usa
requestAnimationFramepara ejecutarse en cada fotograma y actualizar el valor que controla, utilizando el nuevo valor que calcula basado en la curva de animación. -
JS: Se calculan valores intermedios y se pasan a un nodo de propiedades que está adjunto a una
View. -
JS: La
Viewse actualiza mediantesetNativeProps. -
Puente de JS a Native.
-
Nativo: Se actualiza el
UIViewoandroid.View.
Como puedes ver, la mayor parte del trabajo ocurre en el hilo de JS. Si este se bloquea, la animación omitirá fotogramas. También necesita pasar por el puente de JS a Native en cada fotograma para actualizar las vistas nativas.
Lo que hace el controlador nativo es mover todos estos pasos al entorno nativo. Como Animated produce un grafo de nodos animados, puede serializarse y enviarse a native solo una vez al iniciar la animación, eliminando la necesidad de volver al hilo de JS; el código nativo puede encargarse de actualizar las vistas directamente en el hilo de UI en cada fotograma.
Aquí un ejemplo de cómo podemos serializar un valor animado y un nodo de interpolación (no la implementación exacta, solo un ejemplo).
Crea el nodo de valor nativo, este es el valor que se animará:
NativeAnimatedModule.createNode({
id: 1,
type: 'value',
initialValue: 0,
});
Crea el nodo de interpolación nativo, que indica al controlador nativo cómo interpolar un valor:
NativeAnimatedModule.createNode({
id: 2,
type: 'interpolation',
inputRange: [0, 10],
outputRange: [10, 0],
extrapolate: 'clamp',
});
Crea el nodo de propiedades nativo, que indica al controlador nativo a qué propiedad de la vista está conectado:
NativeAnimatedModule.createNode({
id: 3,
type: 'props',
properties: ['style.opacity'],
});
Conecta los nodos entre sí:
NativeAnimatedModule.connectNodes(1, 2);
NativeAnimatedModule.connectNodes(2, 3);
Conecta el nodo de propiedades a una vista:
NativeAnimatedModule.connectToView(3, ReactNative.findNodeHandle(viewRef));
Con esto, el módulo animado nativo tiene toda la información necesaria para actualizar las vistas nativas directamente sin tener que pasar por JS para calcular ningún valor.
Solo queda iniciar la animación especificando el tipo de curva de animación deseado y qué valor animado actualizar. Las animaciones de temporización también pueden simplificarse precalculando cada fotograma de la animación en JS para reducir la implementación nativa.
NativeAnimatedModule.startAnimation({
type: 'timing',
frames: [0, 0.1, 0.2, 0.4, 0.65, ...],
animatedValueId: 1,
});
Y este es el desglose de lo que ocurre cuando se ejecuta la animación:
-
Nativo: El controlador de animación nativo usa
CADisplayLinkoandroid.view.Choreographerpara ejecutarse en cada fotograma y actualizar el valor que controla usando el nuevo valor calculado según la curva de animación. -
Nativo: Se calculan valores intermedios y se pasan a un nodo de propiedades conectado a una vista nativa.
-
Nativo: Se actualiza el
UIViewoandroid.View.
Como puedes ver, ¡no hay más hilo JS ni puente, lo que significa animaciones más rápidas! 🎉🎉
¿Cómo uso esto en mi aplicación?
Para animaciones normales la respuesta es simple: solo agrega useNativeDriver: true a la configuración de animación al iniciarla.
Antes:
Animated.timing(this.state.animatedValue, {
toValue: 1,
duration: 500,
}).start();
Después:
Animated.timing(this.state.animatedValue, {
toValue: 1,
duration: 500,
useNativeDriver: true, // <-- Add this
}).start();
Los valores animados solo son compatibles con un controlador. Si usas el controlador nativo al iniciar una animación en un valor, asegúrate de que todas las animaciones posteriores en ese valor también usen el controlador nativo.
También funciona con Animated.event, lo cual es muy útil para animaciones que deben seguir la posición de desplazamiento, ya que sin el controlador nativo siempre irán un fotograma atrás del gesto debido a la naturaleza asíncrona de React Native.
Antes:
<ScrollView
scrollEventThrottle={16}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: this.state.animatedValue } } }]
)}
>
{content}
</ScrollView>
Después:
<Animated.ScrollView // <-- Use the Animated ScrollView wrapper
scrollEventThrottle={1} // <-- Use 1 here to make sure no events are ever missed
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: this.state.animatedValue } } }],
{ useNativeDriver: true } // <-- Add this
)}
>
{content}
</Animated.ScrollView>
Advertencias
No todo lo que puedes hacer con Animated está actualmente soportado en Animated Nativo. La principal limitación es que solo puedes animar propiedades no relacionadas con el diseño: propiedades como transform y opacity funcionarán, pero Flexbox y propiedades de posición no. Otra limitación es que Animated.event solo funciona con eventos directos, no con eventos de propagación. Esto significa que no funciona con PanResponder pero sí con elementos como ScrollView#onScroll.
Animated Nativo ha sido parte de React Native durante bastante tiempo, pero nunca se documentó porque se consideraba experimental. Por eso, asegúrate de usar una versión reciente (0.40+) de React Native si quieres usar esta función.
Recursos
Para más información sobre Animated, te recomiendo ver esta charla de Christopher Chedeau.
Si quieres un análisis profundo sobre animaciones y cómo delegarlas a nativo puede mejorar la experiencia de usuario, también está esta charla de Krzysztof Magiera.