Animated
The Animated API is React Native's, with one difference that changes how you use it: every
animation runs in the engine. A value bound to a style property is updated in C each frame, with
no JavaScript involved, so animating is cheap even on a 240 MHz core, and useNativeDriver is
accepted and irrelevant.
const pulse = useAnimatedValue(1);
useEffect(() => {
const anim = Animated.loop(
Animated.sequence([
Animated.timing(pulse, {toValue: 1.1, duration: 900}),
Animated.timing(pulse, {toValue: 1.0, duration: 900}),
]),
);
anim.start();
return () => anim.stop();
}, []);
<Animated.Image
source={logo}
style={{width: 64, height: 64, transform: [{scale: pulse}]}}
/>;
Values
new Animated.Value(initial) creates an engine-side float; useAnimatedValue(initial) creates one
tied to the component and destroys it on unmount, which is the form to prefer. A value has:
setValue(v): set it at once, pushing to everything bound to it.destroy(): release its engine slot. The engine's value pool is fixed-size, so anew Animated.Valuethat outlives its screen must be destroyed;useAnimatedValuedoes this for you.interpolate({inputRange, outputRange, extrapolate?, extrapolateLeft?, extrapolateRight?}): a derived value mapped through a piecewise-linear curve, up to 8 breakpoints.outputRangeis numbers, or colors (['#000', 'red']) forbackgroundColorandcolor; a value bound to a rotation axis is read in degrees, sooutputRange: [0, 360]is a full turn.extrapolateis'extend'(default),'clamp'or'identity', with per-end overrides; a color treats'identity'as'clamp'.
Where a value can go
Animated.View, Animated.Text and Animated.Image accept values in opacity and any transform
axis (scale binds both axes), backgroundColor on a View and color on a Text. A color takes only an
interpolation onto colors: a value bound to one directly is ignored with a warning (and rejected by the
AOT). The wrappers are pass-through, kept for
React Native parity: a plain View or Pressable binds an animated style the same way, in both flows.
Flow B binds scale, scaleX, scaleY, translateX, translateY and rotate/rotateZ, not
rotateX/rotateY. Dial also accepts one in value and valueStart.
Animated.createAnimatedComponent wraps another component. A TouchableOpacity owns its own opacity for the press feedback, so animate opacity on
a Pressable instead.
Animations
| Function | Config | Notes |
|---|---|---|
Animated.timing(value, config) | toValue, duration (ms), delay, easing | A curve over a fixed time. duration: 0 finishes at once |
Animated.spring(value, config) | toValue, stiffness (default 100), damping (10), mass (1), velocity, delay | A damped harmonic oscillator, in the engine's physical parameters. React Native's tension/friction and bounciness/speed spellings are not read |
Animated.decay(value, config) | velocity (value per ms), deceleration (per-ms friction, default 0.998) | Coasts from a velocity; there is no target |
Each returns an animation with start(callback?) and stop(). The callback receives
{finished}, false if the animation was stopped or interrupted, and is wired to the engine's own
completion so a chained animation starts exactly when the previous one ends.
Composition
Animated.sequence([...]), Animated.parallel([...], {stopTogether?}),
Animated.stagger(delayMs, [...]), Animated.delay(ms) and
Animated.loop(animation, {iterations?, resetBeforeIteration?}) compose animations as in React
Native. Composition is JavaScript over each child's start/stop, with completion coming from the
engine, so a sequence of timings costs one engine animation at a time.
Flow B compiles all of these, with limits listed in the AOT subset:
a loop can repeat a timing, spring, decay or sequence; a sequence entry must have a known duration;
iterations must be a constant.
Easing
Animated.timing takes an Easing token: Easing.linear, ease, easeIn, easeOut,
easeInOut, quadIn, quadOut, quadInOut, cubicIn, cubicOut, cubicInOut, bounceOut,
elasticOut, and Easing.bezier(x1, y1, x2, y2) for a custom cubic curve. Each maps to a curve the
engine evaluates; a JavaScript easing function is not called per frame.
LayoutAnimation
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
setExpanded(true);
Call configureNext(config, onDone?) before a state change and the next commit tweens every node
whose computed rectangle moved, from where it was to where it now belongs, in the engine. The config
is consumed by that one commit. Presets.easeInEaseOut, Presets.linear and Presets.spring are
the React Native ones (300 ms, 500 ms and a 700 ms spring); the shorthands
LayoutAnimation.easeInEaseOut(onDone?), linear() and spring() call configureNext with that
preset and return nothing; create(duration, type, property), Types and Properties build a custom config. Nodes appearing for the first time
snap into place rather than animating from nothing.
What it costs
An animation the engine drives damages only what it changes: an opacity fade repaints the node, a
translate repaints the old and new positions, a Dial value repaints the swept sliver. Two cases
cost more and are worth knowing:
- A node with
opacitybelow 1, or a transform, composites through scratch memory each frame. Keep such nodes small, and enable the engine's fade cache (ERUI_FADE_CACHE_WIDTHandERUI_FADE_CACHE_HEIGHT) on boards with the RAM for it: after one capture, each frame of a pure opacity animation is a single blend. setStateon a timer is not an animation. SixtysetStates a second re-render, re-diff and re-commit sixty times; atimingon anAnimated.Valuedoes none of that.