diff --git a/Libraries/Animated/Animated.js b/Libraries/Animated/Animated.js index ec4977f3b..1a357f033 100644 --- a/Libraries/Animated/Animated.js +++ b/Libraries/Animated/Animated.js @@ -503,6 +503,12 @@ type ValueListenerCallback = (state: {value: number}) => void; var _uniqueId = 1; +/** + * Standard value for driving animations. One `Animated.Value` can drive + * multiple properties in a synchronized fashion, but can only be driven by one + * mechanism at a time. Using a new mechanism (e.g. starting a new animation, + * or calling `setValue`) will stop any previous ones. + */ class AnimatedValue extends AnimatedWithChildren { _value: number; _offset: number; @@ -526,6 +532,10 @@ class AnimatedValue extends AnimatedWithChildren { return this._value + this._offset; } + /** + * Directly set the value. This will stop any animations running on the value + * and udpate all the bound properties. + */ setValue(value: number): void { if (this._animation) { this._animation.stop(); @@ -534,15 +544,29 @@ class AnimatedValue extends AnimatedWithChildren { this._updateValue(value); } + /** + * Sets an offset that is applied on top of whatever value is set, whether via + * `setValue`, an animation, or `Animated.event`. Useful for compensating + * things like the start of a pan gesture. + */ setOffset(offset: number): void { this._offset = offset; } + /** + * Merges the offset value into the base value and resets the offset to zero. + * The final output of the value is unchanged. + */ flattenOffset(): void { this._value += this._offset; this._offset = 0; } + /** + * Adds an asynchronous listener to the value so you can observe updates from + * animations or whathaveyou. This is useful because there is no way to + * syncronously read the value because it might be driven natively. + */ addListener(callback: ValueListenerCallback): string { var id = String(_uniqueId++); this._listeners[id] = callback; @@ -557,6 +581,30 @@ class AnimatedValue extends AnimatedWithChildren { this._listeners = {}; } + /** + * Stops any running animation or tracking. `callback` is invoked with the + * final value after stopping the animation, which is useful for updating + * state to match the animation position with layout. + */ + stopAnimation(callback?: ?(value: number) => void): void { + this.stopTracking(); + this._animation && this._animation.stop(); + this._animation = null; + callback && callback(this.__getValue()); + } + + /** + * Interpolates the value before updating the property, e.g. mapping 0-1 to + * 0-10. + */ + interpolate(config: InterpolationConfigType): AnimatedInterpolation { + return new AnimatedInterpolation(this, Interpolation.create(config)); + } + + /** + * Typically only used internally, but could be used by a custom Animation + * class. + */ animate(animation: Animation, callback: ?EndCallback): void { var handle = InteractionManager.createInteractionHandle(); var previousAnimation = this._animation; @@ -576,27 +624,22 @@ class AnimatedValue extends AnimatedWithChildren { ); } - stopAnimation(callback?: ?(value: number) => void): void { - this.stopTracking(); - this._animation && this._animation.stop(); - this._animation = null; - callback && callback(this.__getValue()); - } - + /** + * Typically only used internally. + */ stopTracking(): void { this._tracking && this._tracking.__detach(); this._tracking = null; } + /** + * Typically only used internally. + */ track(tracking: Animated): void { this.stopTracking(); this._tracking = tracking; } - interpolate(config: InterpolationConfigType): AnimatedInterpolation { - return new AnimatedInterpolation(this, Interpolation.create(config)); - } - _updateValue(value: number): void { this._value = value; _flush(this); @@ -607,6 +650,45 @@ class AnimatedValue extends AnimatedWithChildren { } type ValueXYListenerCallback = (value: {x: number; y: number}) => void; + +/** + * 2D Value for driving 2D animations, such as pan gestures. Almost identical + * API to normal `Animated.Value`, but multiplexed. Contains two regular + * `Animated.Value`s under the hood. Example: + * + *```javascript + * class DraggableView extends React.Component { + * constructor(props) { + * super(props); + * this.state = { + * pan: new Animated.ValueXY(), // inits to zero + * }; + * this.state.panResponder = PanResponder.create({ + * onStartShouldSetPanResponder: () => true, + * onPanResponderMove: Animated.event([null, { + * dx: this.state.pan.x, // x,y are Animated.Value + * dy: this.state.pan.y, + * }]), + * onPanResponderRelease: () => { + * Animated.spring( + * this.state.pan, // Auto-multiplexed + * {toValue: {x: 0, y: 0}} // Back to zero + * ).start(); + * }, + * }); + * } + * render() { + * return ( + * + * {this.props.children} + * + * ); + * } + * } + *``` + */ class AnimatedValueXY extends AnimatedWithChildren { x: AnimatedValue; y: AnimatedValue; @@ -677,6 +759,13 @@ class AnimatedValueXY extends AnimatedWithChildren { delete this._listeners[id]; } + /** + * Converts `{x, y}` into `{left, top}` for use in style, e.g. + * + *```javascript + * style={this.state.anim.getLayout()} + *``` + */ getLayout(): {[key: string]: AnimatedValue} { return { left: this.x, @@ -684,6 +773,15 @@ class AnimatedValueXY extends AnimatedWithChildren { }; } + /** + * Converts `{x, y}` into a useable translation transform, e.g. + * + *```javascript + * style={{ + * transform: this.state.anim.getTranslateTransform() + * }} + *``` + */ getTranslateTransform(): Array<{[key: string]: AnimatedValue}> { return [ {translateX: this.x}, @@ -1235,21 +1333,6 @@ var stagger = function( type Mapping = {[key: string]: Mapping} | AnimatedValue; -/** - * Takes an array of mappings and extracts values from each arg accordingly, - * then calls setValue on the mapped outputs. e.g. - * - * onScroll={this.AnimatedEvent( - * [{nativeEvent: {contentOffset: {x: this._scrollX}}}] - * {listener} // optional listener invoked asynchronously - * ) - * ... - * onPanResponderMove: this.AnimatedEvent([ - * null, // raw event arg - * {dx: this._panX}, // gestureState arg - * ]), - * - */ type EventConfig = {listener?: ?Function}; var event = function( argMapping: Array, @@ -1287,23 +1370,179 @@ var event = function( }; }; +/** + * Animations are an important part of modern UX, and the `Animated` + * library is designed to make them fluid, powerful, and easy to build and + * maintain. + * + * The simplest workflow is to create an `Animated.Value`, hook it up to one or + * more style attributes of an animated component, and then drive updates either + * via animations, such as `Animated.timing`, or by hooking into gestures like + * panning or scolling via `Animated.event`. `Animated.Value` can also bind to + * props other than style, and can be interpolated as well. Here is a basic + * example of a container view that will fade in when it's mounted: + * + *```javascript + * class FadeInView extends React.Component { + * constructor(props) { + * super(props); + * this.state = { + * fadeAnim: new Animated.Value(0), // init opacity 0 + * }; + * } + * componentDidMount() { + * Animated.timing( // Uses easing functions + * this.state.fadeAnim, // The value to drive + * {toValue: 1}, // Configuration + * ).start(); // Don't forget start! + * } + * render() { + * return ( + * // Binds + * {this.props.children} + * + * ); + * } + * } + *``` + * + * Note that only animatable components can be animated. `View`, `Text`, and + * `Image` are already provided, and you can create custom ones with + * `createAnimatedComponent`. These special components do the magic of binding + * the animated values to the properties, and do targetted native updates to + * avoid the cost of the react render and reconciliation process on every frame. + * They also handle cleanup on unmount so they are safe by default. + * + * Animations are heavily configurable. Custom and pre-defined easing + * functions, delays, durations, decay factors, spring constants, and more can + * all be tweaked depending on the type of animation. + * + * A single `Animated.Value` can drive any number of properties, and each + * property can be run through an interpolation first. An interpolation maps + * input ranges to output ranges, typically using a linear interpolation but + * also supports easing functions. By default, it will extrapolate the curve + * beyond the ranges given, but you can also have it clamp the output value. + * + * For example, you may want to think about your `Animated.Value` as going from + * 0 to 1, but animate the position from 150px to 0px and the opacity from 0 to + * 1. This can easily be done by modifying `style` in the example above like so: + * + *```javascript + * style={{ + * opacity: this.state.fadeAnim, // Binds directly + * transform: [{ + * translateY: this.state.fadeAnim.interpolate({ + * inputRange: [0, 1], + * outputRange: [150, 0] // 0 : 150, 0.5 : 75, 1 : 0 + * }), + * }], + * }}> + *``` + * + * Animations can also be combined in complex ways using composition functions + * such as `sequence` and `parallel`, and can also be chained together simply + * by setting the `toValue` of one animation to be another `Animated.Value`. + * + * `Animated.ValueXY` is handy for 2D animations, like panning, and there are + * other helpful additions like `setOffset` and `getLayout` to aid with typical + * interaction patterns, like drag-and-drop. + * + * You can see more example usage in `AnimationExample.js`, the Gratuitous + * Animation App, and [Animations documentation guide](http://facebook.github.io/react-native/docs/animations.html). + * + * Note that `Animated` is designed to be fully serializable so that animations + * can be run in a high performace way, independent of the normal JavaScript + * event loop. This does influence the API, so keep that in mind when it seems a + * little trickier to do something compared to a fully synchronous system. + * Checkout `Animated.Value.addListener` as a way to work around some of these + * limitations, but use it sparingly since it might have performance + * implications in the future. + */ module.exports = { - delay, - sequence, - parallel, - stagger, + /** + * Standard value class for driving animations. Typically initialized with + * `new Animated.Value(0);` + */ + Value: AnimatedValue, + /** + * 2D value class for driving 2D animations, such as pan gestures. + */ + ValueXY: AnimatedValueXY, + /** + * An animatable View component. + */ + View: createAnimatedComponent(View), + /** + * An animatable Text component. + */ + Text: createAnimatedComponent(Text), + /** + * An animatable Image component. + */ + Image: createAnimatedComponent(Image), + + /** + * Animates a value from an initial velocity to zero based on a decay + * coefficient. + */ decay, + /** + * Animates a value along a timed easing curve. The `Easing` module has tons + * of pre-defined curves, or you can use your own function. + */ timing, + /** + * Spring animation based on Rebound and Origami. Tracks velocity state to + * create fluid motions as the `toValue` updates, and can be chained together. + */ spring, + /** + * Starts an animation after the given delay. + */ + delay, + /** + * Starts an array of animations in order, waiting for each to complete + * before starting the next. If the current running animation is stopped, no + * following animations will be started. + */ + sequence, + /** + * Starts an array of animations all at the same time. By default, if one + * of the animations is stopped, they will all be stopped. You can override + * this with the `stopTogether` flag. + */ + parallel, + /** + * Array of animations may run in parallel (overlap), but are started in + * sequence with successive delays. Nice for doing trailing effects. + */ + stagger, + + /** + * Takes an array of mappings and extracts values from each arg accordingly, + * then calls `setValue` on the mapped outputs. e.g. + * + *```javascript + * onScroll={this.AnimatedEvent( + * [{nativeEvent: {contentOffset: {x: this._scrollX}}}] + * {listener}, // Optional async listener + * ) + * ... + * onPanResponderMove: this.AnimatedEvent([ + * null, // raw event arg ignored + * {dx: this._panX}, // gestureState arg + * ]), + *``` + */ event, - Value: AnimatedValue, - ValueXY: AnimatedValueXY, - __PropsOnlyForTests: AnimatedProps, - View: createAnimatedComponent(View), - Text: createAnimatedComponent(Text), - Image: createAnimatedComponent(Image), + /** + * Make any React component Animatable. Used to create `Animated.View`, etc. + */ createAnimatedComponent, + + __PropsOnlyForTests: AnimatedProps, };