2015-07-07 20:34:09 +00:00
|
|
|
/**
|
|
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* This source code is licensed under the BSD-style license found in the
|
|
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
|
|
*
|
2015-09-22 18:57:08 +00:00
|
|
|
* @providesModule AnimatedImplementation
|
2015-07-07 20:34:09 +00:00
|
|
|
* @flow
|
2016-09-08 12:38:59 +00:00
|
|
|
* @preventMunge
|
2015-07-07 20:34:09 +00:00
|
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
var InteractionManager = require('InteractionManager');
|
|
|
|
var Interpolation = require('Interpolation');
|
2016-11-02 06:55:21 +00:00
|
|
|
var NativeAnimatedHelper = require('NativeAnimatedHelper');
|
2015-07-07 20:34:09 +00:00
|
|
|
var React = require('React');
|
2017-02-09 03:14:03 +00:00
|
|
|
var ReactNative = require('ReactNative');
|
2015-07-07 20:34:09 +00:00
|
|
|
var Set = require('Set');
|
2015-08-03 22:15:07 +00:00
|
|
|
var SpringConfig = require('SpringConfig');
|
2015-10-06 22:19:58 +00:00
|
|
|
var ViewStylePropTypes = require('ViewStylePropTypes');
|
2015-07-07 20:34:09 +00:00
|
|
|
|
|
|
|
var flattenStyle = require('flattenStyle');
|
2016-03-02 12:27:13 +00:00
|
|
|
var invariant = require('fbjs/lib/invariant');
|
|
|
|
var requestAnimationFrame = require('fbjs/lib/requestAnimationFrame');
|
2015-07-07 20:34:09 +00:00
|
|
|
|
2015-10-07 18:52:05 +00:00
|
|
|
import type { InterpolationConfigType } from 'Interpolation';
|
2015-07-07 20:34:09 +00:00
|
|
|
|
|
|
|
type EndResult = {finished: bool};
|
|
|
|
type EndCallback = (result: EndResult) => void;
|
|
|
|
|
2016-03-24 13:18:39 +00:00
|
|
|
var NativeAnimatedAPI = NativeAnimatedHelper.API;
|
|
|
|
|
2016-11-02 06:55:21 +00:00
|
|
|
var warnedMissingNativeAnimated = false;
|
|
|
|
|
|
|
|
function shouldUseNativeDriver(config: AnimationConfig | EventConfig): boolean {
|
|
|
|
if (config.useNativeDriver &&
|
|
|
|
!NativeAnimatedHelper.isNativeAnimatedAvailable()) {
|
|
|
|
if (!warnedMissingNativeAnimated) {
|
|
|
|
console.warn(
|
|
|
|
'Animated: `useNativeDriver` is not supported because the native ' +
|
|
|
|
'animated module is missing. Falling back to JS-based animation. To ' +
|
|
|
|
'resolve this, add `RCTAnimation` module to this app, or remove ' +
|
2016-12-02 23:20:58 +00:00
|
|
|
'`useNativeDriver`. ' +
|
|
|
|
'More info: https://github.com/facebook/react-native/issues/11094#issuecomment-263240420'
|
2016-11-02 06:55:21 +00:00
|
|
|
);
|
|
|
|
warnedMissingNativeAnimated = true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return config.useNativeDriver || false;
|
|
|
|
}
|
|
|
|
|
2015-07-07 20:34:09 +00:00
|
|
|
// Note(vjeux): this would be better as an interface but flow doesn't
|
|
|
|
// support them yet
|
|
|
|
class Animated {
|
2015-08-20 03:06:22 +00:00
|
|
|
__attach(): void {}
|
2016-03-24 13:18:39 +00:00
|
|
|
__detach(): void {
|
|
|
|
if (this.__isNative && this.__nativeTag != null) {
|
|
|
|
NativeAnimatedAPI.dropAnimatedNode(this.__nativeTag);
|
|
|
|
this.__nativeTag = undefined;
|
|
|
|
}
|
|
|
|
}
|
2015-07-07 20:34:09 +00:00
|
|
|
__getValue(): any {}
|
2015-08-20 03:06:22 +00:00
|
|
|
__getAnimatedValue(): any { return this.__getValue(); }
|
|
|
|
__addChild(child: Animated) {}
|
|
|
|
__removeChild(child: Animated) {}
|
|
|
|
__getChildren(): Array<Animated> { return []; }
|
2016-03-24 13:18:39 +00:00
|
|
|
|
|
|
|
/* Methods and props used by native Animated impl */
|
|
|
|
__isNative: bool;
|
|
|
|
__nativeTag: ?number;
|
|
|
|
__makeNative() {
|
|
|
|
if (!this.__isNative) {
|
|
|
|
throw new Error('This node cannot be made a "native" animated node');
|
|
|
|
}
|
|
|
|
}
|
2017-02-28 01:19:00 +00:00
|
|
|
__getNativeTag(): ?number {
|
2016-03-24 13:18:39 +00:00
|
|
|
NativeAnimatedHelper.assertNativeAnimatedModule();
|
|
|
|
invariant(this.__isNative, 'Attempt to get native tag from node not marked as "native"');
|
|
|
|
if (this.__nativeTag == null) {
|
2017-02-28 01:19:00 +00:00
|
|
|
var nativeTag: ?number = NativeAnimatedHelper.generateNewNodeTag();
|
2016-03-24 13:18:39 +00:00
|
|
|
NativeAnimatedAPI.createAnimatedNode(nativeTag, this.__getNativeConfig());
|
|
|
|
this.__nativeTag = nativeTag;
|
|
|
|
}
|
|
|
|
return this.__nativeTag;
|
|
|
|
}
|
|
|
|
__getNativeConfig(): Object {
|
|
|
|
throw new Error('This JS animated node type cannot be used as native animated node');
|
|
|
|
}
|
2016-05-08 08:08:32 +00:00
|
|
|
toJSON(): any { return this.__getValue(); }
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
|
2015-10-15 15:15:28 +00:00
|
|
|
type AnimationConfig = {
|
2016-08-09 11:20:33 +00:00
|
|
|
isInteraction?: bool,
|
|
|
|
useNativeDriver?: bool,
|
2016-08-18 11:47:47 +00:00
|
|
|
onComplete?: ?EndCallback,
|
Adds Animated.loop to Animated API
Summary:
* Any animation can be looped on the javascript thread
* Only basic animations supported natively at this stage, loops run
using the native driver cannot contain animations of type sequence,
parallel, stagger, or loop
Motivation: We need a spinner in our app that is displayed and animated while the javascript thread is tied up with other tasks. This means it needs to be offloaded from the javascript thread, so that it will continue to run while those tasks are churning away.
I originally submitted PR #9513, which has served our needs, but brentvatne pointed out a better way to do it. Had hoped his suggestion would be implemented by janicduplessis or another fb employee, but after 5 months I thought I'd give it another push.
I've put together an implementation that basically matches the suggested API. Let me know what you think, and whether others can pick it up from here and get it in to core.
Personal Motivation: I am leaving my current organisation on Feb 10th, so am trying to clean thing
Closes https://github.com/facebook/react-native/pull/11973
Differential Revision: D4704381
fbshipit-source-id: 42a2cdf5d53a7c0d08f86a58485f7f38739e6cd9
2017-03-14 06:49:47 +00:00
|
|
|
iterations?: number,
|
2015-10-15 15:15:28 +00:00
|
|
|
};
|
|
|
|
|
2015-07-07 20:34:09 +00:00
|
|
|
// Important note: start() and stop() will only be called at most once.
|
|
|
|
// Once an animation has been stopped or finished its course, it will
|
|
|
|
// not be reused.
|
|
|
|
class Animation {
|
|
|
|
__active: bool;
|
2015-10-15 15:15:28 +00:00
|
|
|
__isInteraction: bool;
|
2016-04-22 07:01:55 +00:00
|
|
|
__nativeId: number;
|
2015-07-07 20:34:09 +00:00
|
|
|
__onEnd: ?EndCallback;
|
Adds Animated.loop to Animated API
Summary:
* Any animation can be looped on the javascript thread
* Only basic animations supported natively at this stage, loops run
using the native driver cannot contain animations of type sequence,
parallel, stagger, or loop
Motivation: We need a spinner in our app that is displayed and animated while the javascript thread is tied up with other tasks. This means it needs to be offloaded from the javascript thread, so that it will continue to run while those tasks are churning away.
I originally submitted PR #9513, which has served our needs, but brentvatne pointed out a better way to do it. Had hoped his suggestion would be implemented by janicduplessis or another fb employee, but after 5 months I thought I'd give it another push.
I've put together an implementation that basically matches the suggested API. Let me know what you think, and whether others can pick it up from here and get it in to core.
Personal Motivation: I am leaving my current organisation on Feb 10th, so am trying to clean thing
Closes https://github.com/facebook/react-native/pull/11973
Differential Revision: D4704381
fbshipit-source-id: 42a2cdf5d53a7c0d08f86a58485f7f38739e6cd9
2017-03-14 06:49:47 +00:00
|
|
|
__iterations: number;
|
2015-07-07 20:34:09 +00:00
|
|
|
start(
|
|
|
|
fromValue: number,
|
|
|
|
onUpdate: (value: number) => void,
|
|
|
|
onEnd: ?EndCallback,
|
|
|
|
previousAnimation: ?Animation,
|
2016-03-24 13:18:39 +00:00
|
|
|
animatedValue: AnimatedValue
|
2015-07-07 20:34:09 +00:00
|
|
|
): void {}
|
2016-04-22 07:01:55 +00:00
|
|
|
stop(): void {
|
|
|
|
if (this.__nativeId) {
|
|
|
|
NativeAnimatedAPI.stopAnimation(this.__nativeId);
|
|
|
|
}
|
|
|
|
}
|
2016-08-09 11:04:33 +00:00
|
|
|
__getNativeAnimationConfig(): any {
|
2016-03-24 13:18:39 +00:00
|
|
|
// Subclasses that have corresponding animation implementation done in native
|
|
|
|
// should override this method
|
|
|
|
throw new Error('This animation type cannot be offloaded to native');
|
|
|
|
}
|
2015-07-07 20:34:09 +00:00
|
|
|
// Helper function for subclasses to make sure onEnd is only called once.
|
2016-03-24 13:18:39 +00:00
|
|
|
__debouncedOnEnd(result: EndResult): void {
|
2015-07-07 20:34:09 +00:00
|
|
|
var onEnd = this.__onEnd;
|
|
|
|
this.__onEnd = null;
|
|
|
|
onEnd && onEnd(result);
|
|
|
|
}
|
2016-03-24 13:18:39 +00:00
|
|
|
__startNativeAnimation(animatedValue: AnimatedValue): void {
|
|
|
|
animatedValue.__makeNative();
|
2016-04-22 07:01:55 +00:00
|
|
|
this.__nativeId = NativeAnimatedHelper.generateNewAnimationId();
|
2016-03-24 13:18:39 +00:00
|
|
|
NativeAnimatedAPI.startAnimatingNode(
|
2016-04-22 07:01:55 +00:00
|
|
|
this.__nativeId,
|
2016-03-24 13:18:39 +00:00
|
|
|
animatedValue.__getNativeTag(),
|
2016-08-09 11:04:33 +00:00
|
|
|
this.__getNativeAnimationConfig(),
|
2016-03-24 13:18:39 +00:00
|
|
|
this.__debouncedOnEnd.bind(this)
|
|
|
|
);
|
|
|
|
}
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
class AnimatedWithChildren extends Animated {
|
|
|
|
_children: Array<Animated>;
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
super();
|
|
|
|
this._children = [];
|
|
|
|
}
|
|
|
|
|
2016-03-24 13:18:39 +00:00
|
|
|
__makeNative() {
|
|
|
|
if (!this.__isNative) {
|
|
|
|
this.__isNative = true;
|
|
|
|
for (var child of this._children) {
|
|
|
|
child.__makeNative();
|
|
|
|
NativeAnimatedAPI.connectAnimatedNodes(this.__getNativeTag(), child.__getNativeTag());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-20 03:06:22 +00:00
|
|
|
__addChild(child: Animated): void {
|
2015-07-07 20:34:09 +00:00
|
|
|
if (this._children.length === 0) {
|
2015-08-20 03:06:22 +00:00
|
|
|
this.__attach();
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
this._children.push(child);
|
2016-03-24 13:18:39 +00:00
|
|
|
if (this.__isNative) {
|
|
|
|
// Only accept "native" animated nodes as children
|
|
|
|
child.__makeNative();
|
|
|
|
NativeAnimatedAPI.connectAnimatedNodes(this.__getNativeTag(), child.__getNativeTag());
|
|
|
|
}
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
|
2015-08-20 03:06:22 +00:00
|
|
|
__removeChild(child: Animated): void {
|
2015-07-07 20:34:09 +00:00
|
|
|
var index = this._children.indexOf(child);
|
|
|
|
if (index === -1) {
|
|
|
|
console.warn('Trying to remove a child that doesn\'t exist');
|
|
|
|
return;
|
|
|
|
}
|
2016-03-24 13:18:39 +00:00
|
|
|
if (this.__isNative && child.__isNative) {
|
|
|
|
NativeAnimatedAPI.disconnectAnimatedNodes(this.__getNativeTag(), child.__getNativeTag());
|
|
|
|
}
|
2015-07-07 20:34:09 +00:00
|
|
|
this._children.splice(index, 1);
|
|
|
|
if (this._children.length === 0) {
|
2015-08-20 03:06:22 +00:00
|
|
|
this.__detach();
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-20 03:06:22 +00:00
|
|
|
__getChildren(): Array<Animated> {
|
2015-07-07 20:34:09 +00:00
|
|
|
return this._children;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Animated works by building a directed acyclic graph of dependencies
|
|
|
|
* transparently when you render your Animated components.
|
|
|
|
*
|
|
|
|
* new Animated.Value(0)
|
|
|
|
* .interpolate() .interpolate() new Animated.Value(1)
|
|
|
|
* opacity translateY scale
|
|
|
|
* style transform
|
|
|
|
* View#234 style
|
|
|
|
* View#123
|
|
|
|
*
|
|
|
|
* A) Top Down phase
|
|
|
|
* When an Animated.Value is updated, we recursively go down through this
|
|
|
|
* graph in order to find leaf nodes: the views that we flag as needing
|
|
|
|
* an update.
|
|
|
|
*
|
|
|
|
* B) Bottom Up phase
|
|
|
|
* When a view is flagged as needing an update, we recursively go back up
|
|
|
|
* in order to build the new value that it needs. The reason why we need
|
|
|
|
* this two-phases process is to deal with composite props such as
|
|
|
|
* transform which can receive values from multiple parents.
|
|
|
|
*/
|
|
|
|
function _flush(rootNode: AnimatedValue): void {
|
|
|
|
var animatedStyles = new Set();
|
|
|
|
function findAnimatedStyles(node) {
|
|
|
|
if (typeof node.update === 'function') {
|
|
|
|
animatedStyles.add(node);
|
|
|
|
} else {
|
2015-08-20 03:06:22 +00:00
|
|
|
node.__getChildren().forEach(findAnimatedStyles);
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
findAnimatedStyles(rootNode);
|
2016-03-10 00:53:37 +00:00
|
|
|
/* $FlowFixMe */
|
2015-07-07 20:34:09 +00:00
|
|
|
animatedStyles.forEach(animatedStyle => animatedStyle.update());
|
|
|
|
}
|
|
|
|
|
Adds Animated.loop to Animated API
Summary:
* Any animation can be looped on the javascript thread
* Only basic animations supported natively at this stage, loops run
using the native driver cannot contain animations of type sequence,
parallel, stagger, or loop
Motivation: We need a spinner in our app that is displayed and animated while the javascript thread is tied up with other tasks. This means it needs to be offloaded from the javascript thread, so that it will continue to run while those tasks are churning away.
I originally submitted PR #9513, which has served our needs, but brentvatne pointed out a better way to do it. Had hoped his suggestion would be implemented by janicduplessis or another fb employee, but after 5 months I thought I'd give it another push.
I've put together an implementation that basically matches the suggested API. Let me know what you think, and whether others can pick it up from here and get it in to core.
Personal Motivation: I am leaving my current organisation on Feb 10th, so am trying to clean thing
Closes https://github.com/facebook/react-native/pull/11973
Differential Revision: D4704381
fbshipit-source-id: 42a2cdf5d53a7c0d08f86a58485f7f38739e6cd9
2017-03-14 06:49:47 +00:00
|
|
|
type TimingAnimationConfig = AnimationConfig & {
|
2016-08-09 11:20:33 +00:00
|
|
|
toValue: number | AnimatedValue | {x: number, y: number} | AnimatedValueXY,
|
|
|
|
easing?: (value: number) => number,
|
|
|
|
duration?: number,
|
|
|
|
delay?: number,
|
2015-09-10 06:52:41 +00:00
|
|
|
};
|
|
|
|
|
2015-10-15 15:15:28 +00:00
|
|
|
type TimingAnimationConfigSingle = AnimationConfig & {
|
2016-08-09 11:20:33 +00:00
|
|
|
toValue: number | AnimatedValue,
|
|
|
|
easing?: (value: number) => number,
|
|
|
|
duration?: number,
|
|
|
|
delay?: number,
|
2015-07-07 20:34:09 +00:00
|
|
|
};
|
|
|
|
|
2016-06-14 12:02:16 +00:00
|
|
|
let _easeInOut;
|
|
|
|
function easeInOut() {
|
|
|
|
if (!_easeInOut) {
|
|
|
|
const Easing = require('Easing');
|
|
|
|
_easeInOut = Easing.inOut(Easing.ease);
|
|
|
|
}
|
|
|
|
return _easeInOut;
|
|
|
|
}
|
2015-07-07 20:34:09 +00:00
|
|
|
|
|
|
|
class TimingAnimation extends Animation {
|
|
|
|
_startTime: number;
|
|
|
|
_fromValue: number;
|
2015-09-10 06:52:41 +00:00
|
|
|
_toValue: any;
|
2015-07-07 20:34:09 +00:00
|
|
|
_duration: number;
|
|
|
|
_delay: number;
|
|
|
|
_easing: (value: number) => number;
|
|
|
|
_onUpdate: (value: number) => void;
|
|
|
|
_animationFrame: any;
|
|
|
|
_timeout: any;
|
2016-03-24 13:18:39 +00:00
|
|
|
_useNativeDriver: bool;
|
2015-07-07 20:34:09 +00:00
|
|
|
|
|
|
|
constructor(
|
2015-09-10 06:52:41 +00:00
|
|
|
config: TimingAnimationConfigSingle,
|
2015-07-07 20:34:09 +00:00
|
|
|
) {
|
|
|
|
super();
|
|
|
|
this._toValue = config.toValue;
|
2016-06-14 12:02:16 +00:00
|
|
|
this._easing = config.easing !== undefined ? config.easing : easeInOut();
|
2015-07-07 20:34:09 +00:00
|
|
|
this._duration = config.duration !== undefined ? config.duration : 500;
|
2016-02-02 01:13:08 +00:00
|
|
|
this._delay = config.delay !== undefined ? config.delay : 0;
|
Adds Animated.loop to Animated API
Summary:
* Any animation can be looped on the javascript thread
* Only basic animations supported natively at this stage, loops run
using the native driver cannot contain animations of type sequence,
parallel, stagger, or loop
Motivation: We need a spinner in our app that is displayed and animated while the javascript thread is tied up with other tasks. This means it needs to be offloaded from the javascript thread, so that it will continue to run while those tasks are churning away.
I originally submitted PR #9513, which has served our needs, but brentvatne pointed out a better way to do it. Had hoped his suggestion would be implemented by janicduplessis or another fb employee, but after 5 months I thought I'd give it another push.
I've put together an implementation that basically matches the suggested API. Let me know what you think, and whether others can pick it up from here and get it in to core.
Personal Motivation: I am leaving my current organisation on Feb 10th, so am trying to clean thing
Closes https://github.com/facebook/react-native/pull/11973
Differential Revision: D4704381
fbshipit-source-id: 42a2cdf5d53a7c0d08f86a58485f7f38739e6cd9
2017-03-14 06:49:47 +00:00
|
|
|
this.__iterations = config.iterations !== undefined ? config.iterations : 1;
|
2015-10-15 15:15:28 +00:00
|
|
|
this.__isInteraction = config.isInteraction !== undefined ? config.isInteraction : true;
|
2016-11-02 06:55:21 +00:00
|
|
|
this._useNativeDriver = shouldUseNativeDriver(config);
|
2016-03-24 13:18:39 +00:00
|
|
|
}
|
|
|
|
|
2016-08-09 11:04:33 +00:00
|
|
|
__getNativeAnimationConfig(): any {
|
2016-03-24 13:18:39 +00:00
|
|
|
var frameDuration = 1000.0 / 60.0;
|
|
|
|
var frames = [];
|
2016-06-30 12:18:31 +00:00
|
|
|
for (var dt = 0.0; dt < this._duration; dt += frameDuration) {
|
2016-03-24 13:18:39 +00:00
|
|
|
frames.push(this._easing(dt / this._duration));
|
|
|
|
}
|
2016-06-30 12:18:31 +00:00
|
|
|
frames.push(this._easing(1));
|
2016-03-24 13:18:39 +00:00
|
|
|
return {
|
|
|
|
type: 'frames',
|
|
|
|
frames,
|
|
|
|
toValue: this._toValue,
|
Adds Animated.loop to Animated API
Summary:
* Any animation can be looped on the javascript thread
* Only basic animations supported natively at this stage, loops run
using the native driver cannot contain animations of type sequence,
parallel, stagger, or loop
Motivation: We need a spinner in our app that is displayed and animated while the javascript thread is tied up with other tasks. This means it needs to be offloaded from the javascript thread, so that it will continue to run while those tasks are churning away.
I originally submitted PR #9513, which has served our needs, but brentvatne pointed out a better way to do it. Had hoped his suggestion would be implemented by janicduplessis or another fb employee, but after 5 months I thought I'd give it another push.
I've put together an implementation that basically matches the suggested API. Let me know what you think, and whether others can pick it up from here and get it in to core.
Personal Motivation: I am leaving my current organisation on Feb 10th, so am trying to clean thing
Closes https://github.com/facebook/react-native/pull/11973
Differential Revision: D4704381
fbshipit-source-id: 42a2cdf5d53a7c0d08f86a58485f7f38739e6cd9
2017-03-14 06:49:47 +00:00
|
|
|
iterations: this.__iterations,
|
2016-03-24 13:18:39 +00:00
|
|
|
};
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
start(
|
|
|
|
fromValue: number,
|
|
|
|
onUpdate: (value: number) => void,
|
|
|
|
onEnd: ?EndCallback,
|
2016-03-24 13:18:39 +00:00
|
|
|
previousAnimation: ?Animation,
|
|
|
|
animatedValue: AnimatedValue
|
2015-07-07 20:34:09 +00:00
|
|
|
): void {
|
|
|
|
this.__active = true;
|
|
|
|
this._fromValue = fromValue;
|
|
|
|
this._onUpdate = onUpdate;
|
|
|
|
this.__onEnd = onEnd;
|
|
|
|
|
|
|
|
var start = () => {
|
2016-11-14 17:29:18 +00:00
|
|
|
// Animations that sometimes have 0 duration and sometimes do not
|
|
|
|
// still need to use the native driver when duration is 0 so as to
|
|
|
|
// not cause intermixed JS and native animations.
|
|
|
|
if (this._duration === 0 && !this._useNativeDriver) {
|
2015-07-07 20:34:09 +00:00
|
|
|
this._onUpdate(this._toValue);
|
2015-07-08 11:17:54 +00:00
|
|
|
this.__debouncedOnEnd({finished: true});
|
2015-07-07 20:34:09 +00:00
|
|
|
} else {
|
|
|
|
this._startTime = Date.now();
|
2016-03-24 13:18:39 +00:00
|
|
|
if (this._useNativeDriver) {
|
|
|
|
this.__startNativeAnimation(animatedValue);
|
|
|
|
} else {
|
|
|
|
this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this));
|
|
|
|
}
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
if (this._delay) {
|
|
|
|
this._timeout = setTimeout(start, this._delay);
|
|
|
|
} else {
|
|
|
|
start();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
onUpdate(): void {
|
|
|
|
var now = Date.now();
|
|
|
|
if (now >= this._startTime + this._duration) {
|
|
|
|
if (this._duration === 0) {
|
|
|
|
this._onUpdate(this._toValue);
|
|
|
|
} else {
|
|
|
|
this._onUpdate(
|
|
|
|
this._fromValue + this._easing(1) * (this._toValue - this._fromValue)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
this.__debouncedOnEnd({finished: true});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this._onUpdate(
|
|
|
|
this._fromValue +
|
|
|
|
this._easing((now - this._startTime) / this._duration) *
|
|
|
|
(this._toValue - this._fromValue)
|
|
|
|
);
|
|
|
|
if (this.__active) {
|
2015-07-09 08:45:33 +00:00
|
|
|
this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this));
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
stop(): void {
|
2016-04-22 07:01:55 +00:00
|
|
|
super.stop();
|
2015-07-07 20:34:09 +00:00
|
|
|
this.__active = false;
|
|
|
|
clearTimeout(this._timeout);
|
2016-06-30 08:57:30 +00:00
|
|
|
global.cancelAnimationFrame(this._animationFrame);
|
2015-07-07 20:34:09 +00:00
|
|
|
this.__debouncedOnEnd({finished: false});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-15 15:15:28 +00:00
|
|
|
type DecayAnimationConfig = AnimationConfig & {
|
2016-08-09 11:20:33 +00:00
|
|
|
velocity: number | {x: number, y: number},
|
|
|
|
deceleration?: number,
|
2015-07-07 20:34:09 +00:00
|
|
|
};
|
|
|
|
|
2015-10-15 15:15:28 +00:00
|
|
|
type DecayAnimationConfigSingle = AnimationConfig & {
|
2016-08-09 11:20:33 +00:00
|
|
|
velocity: number,
|
|
|
|
deceleration?: number,
|
2015-07-07 20:34:09 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
class DecayAnimation extends Animation {
|
|
|
|
_startTime: number;
|
|
|
|
_lastValue: number;
|
|
|
|
_fromValue: number;
|
|
|
|
_deceleration: number;
|
|
|
|
_velocity: number;
|
|
|
|
_onUpdate: (value: number) => void;
|
|
|
|
_animationFrame: any;
|
2016-08-19 14:06:29 +00:00
|
|
|
_useNativeDriver: bool;
|
2015-07-07 20:34:09 +00:00
|
|
|
|
|
|
|
constructor(
|
|
|
|
config: DecayAnimationConfigSingle,
|
|
|
|
) {
|
|
|
|
super();
|
2016-02-02 01:13:08 +00:00
|
|
|
this._deceleration = config.deceleration !== undefined ? config.deceleration : 0.998;
|
2015-07-07 20:34:09 +00:00
|
|
|
this._velocity = config.velocity;
|
2016-11-02 06:55:21 +00:00
|
|
|
this._useNativeDriver = shouldUseNativeDriver(config);
|
2015-10-15 15:15:28 +00:00
|
|
|
this.__isInteraction = config.isInteraction !== undefined ? config.isInteraction : true;
|
Adds Animated.loop to Animated API
Summary:
* Any animation can be looped on the javascript thread
* Only basic animations supported natively at this stage, loops run
using the native driver cannot contain animations of type sequence,
parallel, stagger, or loop
Motivation: We need a spinner in our app that is displayed and animated while the javascript thread is tied up with other tasks. This means it needs to be offloaded from the javascript thread, so that it will continue to run while those tasks are churning away.
I originally submitted PR #9513, which has served our needs, but brentvatne pointed out a better way to do it. Had hoped his suggestion would be implemented by janicduplessis or another fb employee, but after 5 months I thought I'd give it another push.
I've put together an implementation that basically matches the suggested API. Let me know what you think, and whether others can pick it up from here and get it in to core.
Personal Motivation: I am leaving my current organisation on Feb 10th, so am trying to clean thing
Closes https://github.com/facebook/react-native/pull/11973
Differential Revision: D4704381
fbshipit-source-id: 42a2cdf5d53a7c0d08f86a58485f7f38739e6cd9
2017-03-14 06:49:47 +00:00
|
|
|
this.__iterations = config.iterations !== undefined ? config.iterations : 1;
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
|
2016-08-19 14:06:29 +00:00
|
|
|
__getNativeAnimationConfig() {
|
|
|
|
return {
|
|
|
|
type: 'decay',
|
|
|
|
deceleration: this._deceleration,
|
|
|
|
velocity: this._velocity,
|
Adds Animated.loop to Animated API
Summary:
* Any animation can be looped on the javascript thread
* Only basic animations supported natively at this stage, loops run
using the native driver cannot contain animations of type sequence,
parallel, stagger, or loop
Motivation: We need a spinner in our app that is displayed and animated while the javascript thread is tied up with other tasks. This means it needs to be offloaded from the javascript thread, so that it will continue to run while those tasks are churning away.
I originally submitted PR #9513, which has served our needs, but brentvatne pointed out a better way to do it. Had hoped his suggestion would be implemented by janicduplessis or another fb employee, but after 5 months I thought I'd give it another push.
I've put together an implementation that basically matches the suggested API. Let me know what you think, and whether others can pick it up from here and get it in to core.
Personal Motivation: I am leaving my current organisation on Feb 10th, so am trying to clean thing
Closes https://github.com/facebook/react-native/pull/11973
Differential Revision: D4704381
fbshipit-source-id: 42a2cdf5d53a7c0d08f86a58485f7f38739e6cd9
2017-03-14 06:49:47 +00:00
|
|
|
iterations: this.__iterations,
|
2016-08-19 14:06:29 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2015-07-07 20:34:09 +00:00
|
|
|
start(
|
|
|
|
fromValue: number,
|
|
|
|
onUpdate: (value: number) => void,
|
|
|
|
onEnd: ?EndCallback,
|
2016-08-19 14:06:29 +00:00
|
|
|
previousAnimation: ?Animation,
|
|
|
|
animatedValue: AnimatedValue,
|
2015-07-07 20:34:09 +00:00
|
|
|
): void {
|
|
|
|
this.__active = true;
|
|
|
|
this._lastValue = fromValue;
|
|
|
|
this._fromValue = fromValue;
|
|
|
|
this._onUpdate = onUpdate;
|
|
|
|
this.__onEnd = onEnd;
|
|
|
|
this._startTime = Date.now();
|
2016-08-19 14:06:29 +00:00
|
|
|
if (this._useNativeDriver) {
|
|
|
|
this.__startNativeAnimation(animatedValue);
|
|
|
|
} else {
|
|
|
|
this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this));
|
|
|
|
}
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
onUpdate(): void {
|
|
|
|
var now = Date.now();
|
|
|
|
|
|
|
|
var value = this._fromValue +
|
|
|
|
(this._velocity / (1 - this._deceleration)) *
|
|
|
|
(1 - Math.exp(-(1 - this._deceleration) * (now - this._startTime)));
|
|
|
|
|
|
|
|
this._onUpdate(value);
|
|
|
|
|
|
|
|
if (Math.abs(this._lastValue - value) < 0.1) {
|
|
|
|
this.__debouncedOnEnd({finished: true});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this._lastValue = value;
|
|
|
|
if (this.__active) {
|
2015-07-09 08:45:33 +00:00
|
|
|
this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this));
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
stop(): void {
|
2016-04-22 07:01:55 +00:00
|
|
|
super.stop();
|
2015-07-07 20:34:09 +00:00
|
|
|
this.__active = false;
|
2016-06-30 08:57:30 +00:00
|
|
|
global.cancelAnimationFrame(this._animationFrame);
|
2015-07-07 20:34:09 +00:00
|
|
|
this.__debouncedOnEnd({finished: false});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-15 15:15:28 +00:00
|
|
|
type SpringAnimationConfig = AnimationConfig & {
|
2016-08-09 11:20:33 +00:00
|
|
|
toValue: number | AnimatedValue | {x: number, y: number} | AnimatedValueXY,
|
|
|
|
overshootClamping?: bool,
|
|
|
|
restDisplacementThreshold?: number,
|
|
|
|
restSpeedThreshold?: number,
|
|
|
|
velocity?: number | {x: number, y: number},
|
|
|
|
bounciness?: number,
|
|
|
|
speed?: number,
|
|
|
|
tension?: number,
|
|
|
|
friction?: number,
|
2015-07-07 20:34:09 +00:00
|
|
|
};
|
|
|
|
|
2015-10-15 15:15:28 +00:00
|
|
|
type SpringAnimationConfigSingle = AnimationConfig & {
|
2016-08-09 11:20:33 +00:00
|
|
|
toValue: number | AnimatedValue,
|
|
|
|
overshootClamping?: bool,
|
|
|
|
restDisplacementThreshold?: number,
|
|
|
|
restSpeedThreshold?: number,
|
|
|
|
velocity?: number,
|
|
|
|
bounciness?: number,
|
|
|
|
speed?: number,
|
|
|
|
tension?: number,
|
|
|
|
friction?: number,
|
2015-07-07 20:34:09 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
function withDefault<T>(value: ?T, defaultValue: T): T {
|
|
|
|
if (value === undefined || value === null) {
|
|
|
|
return defaultValue;
|
|
|
|
}
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
class SpringAnimation extends Animation {
|
|
|
|
_overshootClamping: bool;
|
|
|
|
_restDisplacementThreshold: number;
|
|
|
|
_restSpeedThreshold: number;
|
|
|
|
_initialVelocity: ?number;
|
|
|
|
_lastVelocity: number;
|
|
|
|
_startPosition: number;
|
|
|
|
_lastPosition: number;
|
|
|
|
_fromValue: number;
|
|
|
|
_toValue: any;
|
|
|
|
_tension: number;
|
|
|
|
_friction: number;
|
|
|
|
_lastTime: number;
|
|
|
|
_onUpdate: (value: number) => void;
|
|
|
|
_animationFrame: any;
|
2016-08-05 19:01:49 +00:00
|
|
|
_useNativeDriver: bool;
|
2015-07-07 20:34:09 +00:00
|
|
|
|
|
|
|
constructor(
|
|
|
|
config: SpringAnimationConfigSingle,
|
|
|
|
) {
|
|
|
|
super();
|
|
|
|
|
|
|
|
this._overshootClamping = withDefault(config.overshootClamping, false);
|
|
|
|
this._restDisplacementThreshold = withDefault(config.restDisplacementThreshold, 0.001);
|
|
|
|
this._restSpeedThreshold = withDefault(config.restSpeedThreshold, 0.001);
|
|
|
|
this._initialVelocity = config.velocity;
|
|
|
|
this._lastVelocity = withDefault(config.velocity, 0);
|
|
|
|
this._toValue = config.toValue;
|
2016-11-02 06:55:21 +00:00
|
|
|
this._useNativeDriver = shouldUseNativeDriver(config);
|
2015-10-15 15:15:28 +00:00
|
|
|
this.__isInteraction = config.isInteraction !== undefined ? config.isInteraction : true;
|
Adds Animated.loop to Animated API
Summary:
* Any animation can be looped on the javascript thread
* Only basic animations supported natively at this stage, loops run
using the native driver cannot contain animations of type sequence,
parallel, stagger, or loop
Motivation: We need a spinner in our app that is displayed and animated while the javascript thread is tied up with other tasks. This means it needs to be offloaded from the javascript thread, so that it will continue to run while those tasks are churning away.
I originally submitted PR #9513, which has served our needs, but brentvatne pointed out a better way to do it. Had hoped his suggestion would be implemented by janicduplessis or another fb employee, but after 5 months I thought I'd give it another push.
I've put together an implementation that basically matches the suggested API. Let me know what you think, and whether others can pick it up from here and get it in to core.
Personal Motivation: I am leaving my current organisation on Feb 10th, so am trying to clean thing
Closes https://github.com/facebook/react-native/pull/11973
Differential Revision: D4704381
fbshipit-source-id: 42a2cdf5d53a7c0d08f86a58485f7f38739e6cd9
2017-03-14 06:49:47 +00:00
|
|
|
this.__iterations = config.iterations !== undefined ? config.iterations : 1;
|
2015-07-07 20:34:09 +00:00
|
|
|
|
|
|
|
var springConfig;
|
|
|
|
if (config.bounciness !== undefined || config.speed !== undefined) {
|
|
|
|
invariant(
|
|
|
|
config.tension === undefined && config.friction === undefined,
|
|
|
|
'You can only define bounciness/speed or tension/friction but not both',
|
|
|
|
);
|
2015-08-03 22:15:07 +00:00
|
|
|
springConfig = SpringConfig.fromBouncinessAndSpeed(
|
2015-07-07 20:34:09 +00:00
|
|
|
withDefault(config.bounciness, 8),
|
|
|
|
withDefault(config.speed, 12),
|
|
|
|
);
|
|
|
|
} else {
|
2015-08-03 22:15:07 +00:00
|
|
|
springConfig = SpringConfig.fromOrigamiTensionAndFriction(
|
2015-07-07 20:34:09 +00:00
|
|
|
withDefault(config.tension, 40),
|
|
|
|
withDefault(config.friction, 7),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
this._tension = springConfig.tension;
|
|
|
|
this._friction = springConfig.friction;
|
|
|
|
}
|
|
|
|
|
2016-08-09 11:04:33 +00:00
|
|
|
__getNativeAnimationConfig() {
|
2016-08-05 19:01:49 +00:00
|
|
|
return {
|
|
|
|
type: 'spring',
|
|
|
|
overshootClamping: this._overshootClamping,
|
|
|
|
restDisplacementThreshold: this._restDisplacementThreshold,
|
|
|
|
restSpeedThreshold: this._restSpeedThreshold,
|
|
|
|
tension: this._tension,
|
|
|
|
friction: this._friction,
|
|
|
|
initialVelocity: withDefault(this._initialVelocity, this._lastVelocity),
|
|
|
|
toValue: this._toValue,
|
Adds Animated.loop to Animated API
Summary:
* Any animation can be looped on the javascript thread
* Only basic animations supported natively at this stage, loops run
using the native driver cannot contain animations of type sequence,
parallel, stagger, or loop
Motivation: We need a spinner in our app that is displayed and animated while the javascript thread is tied up with other tasks. This means it needs to be offloaded from the javascript thread, so that it will continue to run while those tasks are churning away.
I originally submitted PR #9513, which has served our needs, but brentvatne pointed out a better way to do it. Had hoped his suggestion would be implemented by janicduplessis or another fb employee, but after 5 months I thought I'd give it another push.
I've put together an implementation that basically matches the suggested API. Let me know what you think, and whether others can pick it up from here and get it in to core.
Personal Motivation: I am leaving my current organisation on Feb 10th, so am trying to clean thing
Closes https://github.com/facebook/react-native/pull/11973
Differential Revision: D4704381
fbshipit-source-id: 42a2cdf5d53a7c0d08f86a58485f7f38739e6cd9
2017-03-14 06:49:47 +00:00
|
|
|
iterations: this.__iterations,
|
2016-08-05 19:01:49 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2015-07-07 20:34:09 +00:00
|
|
|
start(
|
|
|
|
fromValue: number,
|
|
|
|
onUpdate: (value: number) => void,
|
|
|
|
onEnd: ?EndCallback,
|
|
|
|
previousAnimation: ?Animation,
|
2016-08-05 19:01:49 +00:00
|
|
|
animatedValue: AnimatedValue
|
2015-07-07 20:34:09 +00:00
|
|
|
): void {
|
|
|
|
this.__active = true;
|
|
|
|
this._startPosition = fromValue;
|
|
|
|
this._lastPosition = this._startPosition;
|
|
|
|
|
|
|
|
this._onUpdate = onUpdate;
|
|
|
|
this.__onEnd = onEnd;
|
|
|
|
this._lastTime = Date.now();
|
|
|
|
|
|
|
|
if (previousAnimation instanceof SpringAnimation) {
|
|
|
|
var internalState = previousAnimation.getInternalState();
|
|
|
|
this._lastPosition = internalState.lastPosition;
|
|
|
|
this._lastVelocity = internalState.lastVelocity;
|
|
|
|
this._lastTime = internalState.lastTime;
|
|
|
|
}
|
|
|
|
if (this._initialVelocity !== undefined &&
|
|
|
|
this._initialVelocity !== null) {
|
|
|
|
this._lastVelocity = this._initialVelocity;
|
|
|
|
}
|
2016-08-05 19:01:49 +00:00
|
|
|
if (this._useNativeDriver) {
|
|
|
|
this.__startNativeAnimation(animatedValue);
|
|
|
|
} else {
|
|
|
|
this.onUpdate();
|
|
|
|
}
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
getInternalState(): Object {
|
|
|
|
return {
|
|
|
|
lastPosition: this._lastPosition,
|
|
|
|
lastVelocity: this._lastVelocity,
|
|
|
|
lastTime: this._lastTime,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
onUpdate(): void {
|
|
|
|
var position = this._lastPosition;
|
|
|
|
var velocity = this._lastVelocity;
|
|
|
|
|
|
|
|
var tempPosition = this._lastPosition;
|
|
|
|
var tempVelocity = this._lastVelocity;
|
|
|
|
|
|
|
|
// If for some reason we lost a lot of frames (e.g. process large payload or
|
|
|
|
// stopped in the debugger), we only advance by 4 frames worth of
|
|
|
|
// computation and will continue on the next frame. It's better to have it
|
|
|
|
// running at faster speed than jumping to the end.
|
|
|
|
var MAX_STEPS = 64;
|
|
|
|
var now = Date.now();
|
|
|
|
if (now > this._lastTime + MAX_STEPS) {
|
|
|
|
now = this._lastTime + MAX_STEPS;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We are using a fixed time step and a maximum number of iterations.
|
|
|
|
// The following post provides a lot of thoughts into how to build this
|
|
|
|
// loop: http://gafferongames.com/game-physics/fix-your-timestep/
|
|
|
|
var TIMESTEP_MSEC = 1;
|
|
|
|
var numSteps = Math.floor((now - this._lastTime) / TIMESTEP_MSEC);
|
|
|
|
|
|
|
|
for (var i = 0; i < numSteps; ++i) {
|
|
|
|
// Velocity is based on seconds instead of milliseconds
|
|
|
|
var step = TIMESTEP_MSEC / 1000;
|
|
|
|
|
|
|
|
// This is using RK4. A good blog post to understand how it works:
|
|
|
|
// http://gafferongames.com/game-physics/integration-basics/
|
|
|
|
var aVelocity = velocity;
|
2016-08-11 11:18:45 +00:00
|
|
|
var aAcceleration = this._tension *
|
|
|
|
(this._toValue - tempPosition) - this._friction * tempVelocity;
|
2015-07-07 20:34:09 +00:00
|
|
|
var tempPosition = position + aVelocity * step / 2;
|
|
|
|
var tempVelocity = velocity + aAcceleration * step / 2;
|
|
|
|
|
|
|
|
var bVelocity = tempVelocity;
|
2016-08-11 11:18:45 +00:00
|
|
|
var bAcceleration = this._tension *
|
|
|
|
(this._toValue - tempPosition) - this._friction * tempVelocity;
|
2015-07-07 20:34:09 +00:00
|
|
|
tempPosition = position + bVelocity * step / 2;
|
|
|
|
tempVelocity = velocity + bAcceleration * step / 2;
|
|
|
|
|
|
|
|
var cVelocity = tempVelocity;
|
2016-08-11 11:18:45 +00:00
|
|
|
var cAcceleration = this._tension *
|
|
|
|
(this._toValue - tempPosition) - this._friction * tempVelocity;
|
2015-07-07 20:34:09 +00:00
|
|
|
tempPosition = position + cVelocity * step / 2;
|
|
|
|
tempVelocity = velocity + cAcceleration * step / 2;
|
|
|
|
|
|
|
|
var dVelocity = tempVelocity;
|
2016-08-11 11:18:45 +00:00
|
|
|
var dAcceleration = this._tension *
|
|
|
|
(this._toValue - tempPosition) - this._friction * tempVelocity;
|
2015-07-07 20:34:09 +00:00
|
|
|
tempPosition = position + cVelocity * step / 2;
|
|
|
|
tempVelocity = velocity + cAcceleration * step / 2;
|
|
|
|
|
|
|
|
var dxdt = (aVelocity + 2 * (bVelocity + cVelocity) + dVelocity) / 6;
|
|
|
|
var dvdt = (aAcceleration + 2 * (bAcceleration + cAcceleration) + dAcceleration) / 6;
|
|
|
|
|
|
|
|
position += dxdt * step;
|
|
|
|
velocity += dvdt * step;
|
|
|
|
}
|
|
|
|
|
|
|
|
this._lastTime = now;
|
|
|
|
this._lastPosition = position;
|
|
|
|
this._lastVelocity = velocity;
|
|
|
|
|
|
|
|
this._onUpdate(position);
|
|
|
|
if (!this.__active) { // a listener might have stopped us in _onUpdate
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Conditions for stopping the spring animation
|
|
|
|
var isOvershooting = false;
|
|
|
|
if (this._overshootClamping && this._tension !== 0) {
|
|
|
|
if (this._startPosition < this._toValue) {
|
|
|
|
isOvershooting = position > this._toValue;
|
|
|
|
} else {
|
|
|
|
isOvershooting = position < this._toValue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var isVelocity = Math.abs(velocity) <= this._restSpeedThreshold;
|
|
|
|
var isDisplacement = true;
|
|
|
|
if (this._tension !== 0) {
|
|
|
|
isDisplacement = Math.abs(this._toValue - position) <= this._restDisplacementThreshold;
|
|
|
|
}
|
2015-07-23 00:20:27 +00:00
|
|
|
|
2015-07-07 20:34:09 +00:00
|
|
|
if (isOvershooting || (isVelocity && isDisplacement)) {
|
2015-07-23 00:20:27 +00:00
|
|
|
if (this._tension !== 0) {
|
|
|
|
// Ensure that we end up with a round value
|
|
|
|
this._onUpdate(this._toValue);
|
|
|
|
}
|
|
|
|
|
2015-07-07 20:34:09 +00:00
|
|
|
this.__debouncedOnEnd({finished: true});
|
|
|
|
return;
|
|
|
|
}
|
2015-07-09 08:45:33 +00:00
|
|
|
this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this));
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
stop(): void {
|
2016-04-22 07:01:55 +00:00
|
|
|
super.stop();
|
2015-07-07 20:34:09 +00:00
|
|
|
this.__active = false;
|
2016-06-30 08:57:30 +00:00
|
|
|
global.cancelAnimationFrame(this._animationFrame);
|
2015-07-07 20:34:09 +00:00
|
|
|
this.__debouncedOnEnd({finished: false});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type ValueListenerCallback = (state: {value: number}) => void;
|
|
|
|
|
|
|
|
var _uniqueId = 1;
|
|
|
|
|
2015-09-04 02:29:04 +00:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
2015-07-07 20:34:09 +00:00
|
|
|
class AnimatedValue extends AnimatedWithChildren {
|
|
|
|
_value: number;
|
Adds Animated.loop to Animated API
Summary:
* Any animation can be looped on the javascript thread
* Only basic animations supported natively at this stage, loops run
using the native driver cannot contain animations of type sequence,
parallel, stagger, or loop
Motivation: We need a spinner in our app that is displayed and animated while the javascript thread is tied up with other tasks. This means it needs to be offloaded from the javascript thread, so that it will continue to run while those tasks are churning away.
I originally submitted PR #9513, which has served our needs, but brentvatne pointed out a better way to do it. Had hoped his suggestion would be implemented by janicduplessis or another fb employee, but after 5 months I thought I'd give it another push.
I've put together an implementation that basically matches the suggested API. Let me know what you think, and whether others can pick it up from here and get it in to core.
Personal Motivation: I am leaving my current organisation on Feb 10th, so am trying to clean thing
Closes https://github.com/facebook/react-native/pull/11973
Differential Revision: D4704381
fbshipit-source-id: 42a2cdf5d53a7c0d08f86a58485f7f38739e6cd9
2017-03-14 06:49:47 +00:00
|
|
|
_startingValue: number;
|
2015-07-07 20:34:09 +00:00
|
|
|
_offset: number;
|
|
|
|
_animation: ?Animation;
|
|
|
|
_tracking: ?Animated;
|
|
|
|
_listeners: {[key: string]: ValueListenerCallback};
|
2016-08-04 20:11:37 +00:00
|
|
|
__nativeAnimatedValueListener: ?any;
|
2015-07-07 20:34:09 +00:00
|
|
|
|
|
|
|
constructor(value: number) {
|
|
|
|
super();
|
Adds Animated.loop to Animated API
Summary:
* Any animation can be looped on the javascript thread
* Only basic animations supported natively at this stage, loops run
using the native driver cannot contain animations of type sequence,
parallel, stagger, or loop
Motivation: We need a spinner in our app that is displayed and animated while the javascript thread is tied up with other tasks. This means it needs to be offloaded from the javascript thread, so that it will continue to run while those tasks are churning away.
I originally submitted PR #9513, which has served our needs, but brentvatne pointed out a better way to do it. Had hoped his suggestion would be implemented by janicduplessis or another fb employee, but after 5 months I thought I'd give it another push.
I've put together an implementation that basically matches the suggested API. Let me know what you think, and whether others can pick it up from here and get it in to core.
Personal Motivation: I am leaving my current organisation on Feb 10th, so am trying to clean thing
Closes https://github.com/facebook/react-native/pull/11973
Differential Revision: D4704381
fbshipit-source-id: 42a2cdf5d53a7c0d08f86a58485f7f38739e6cd9
2017-03-14 06:49:47 +00:00
|
|
|
this._startingValue = this._value = value;
|
2015-07-07 20:34:09 +00:00
|
|
|
this._offset = 0;
|
|
|
|
this._animation = null;
|
|
|
|
this._listeners = {};
|
|
|
|
}
|
|
|
|
|
2015-08-20 03:06:22 +00:00
|
|
|
__detach() {
|
2015-07-07 20:34:09 +00:00
|
|
|
this.stopAnimation();
|
2016-03-24 13:18:39 +00:00
|
|
|
super.__detach();
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
__getValue(): number {
|
|
|
|
return this._value + this._offset;
|
|
|
|
}
|
|
|
|
|
2016-08-04 20:11:37 +00:00
|
|
|
__makeNative() {
|
|
|
|
super.__makeNative();
|
|
|
|
|
|
|
|
if (Object.keys(this._listeners).length) {
|
|
|
|
this._startListeningToNativeValueUpdates();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-04 02:29:04 +00:00
|
|
|
/**
|
|
|
|
* Directly set the value. This will stop any animations running on the value
|
2015-11-18 16:08:47 +00:00
|
|
|
* and update all the bound properties.
|
2015-09-04 02:29:04 +00:00
|
|
|
*/
|
2015-07-07 20:34:09 +00:00
|
|
|
setValue(value: number): void {
|
|
|
|
if (this._animation) {
|
|
|
|
this._animation.stop();
|
|
|
|
this._animation = null;
|
|
|
|
}
|
2016-08-11 11:18:45 +00:00
|
|
|
this._updateValue(
|
|
|
|
value,
|
|
|
|
!this.__isNative /* don't perform a flush for natively driven values */);
|
2016-03-24 13:18:39 +00:00
|
|
|
if (this.__isNative) {
|
|
|
|
NativeAnimatedAPI.setAnimatedNodeValue(this.__getNativeTag(), value);
|
|
|
|
}
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
|
2015-09-04 02:29:04 +00:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
2015-07-07 20:34:09 +00:00
|
|
|
setOffset(offset: number): void {
|
|
|
|
this._offset = offset;
|
2016-09-26 17:17:29 +00:00
|
|
|
if (this.__isNative) {
|
|
|
|
NativeAnimatedAPI.setAnimatedNodeOffset(this.__getNativeTag(), offset);
|
|
|
|
}
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
|
2015-09-04 02:29:04 +00:00
|
|
|
/**
|
|
|
|
* Merges the offset value into the base value and resets the offset to zero.
|
|
|
|
* The final output of the value is unchanged.
|
|
|
|
*/
|
2015-07-07 20:34:09 +00:00
|
|
|
flattenOffset(): void {
|
|
|
|
this._value += this._offset;
|
|
|
|
this._offset = 0;
|
2016-09-26 17:17:29 +00:00
|
|
|
if (this.__isNative) {
|
|
|
|
NativeAnimatedAPI.flattenAnimatedNodeOffset(this.__getNativeTag());
|
|
|
|
}
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
|
2016-11-08 04:36:52 +00:00
|
|
|
/**
|
|
|
|
* Sets the offset value to the base value, and resets the base value to zero.
|
|
|
|
* The final output of the value is unchanged.
|
|
|
|
*/
|
|
|
|
extractOffset(): void {
|
|
|
|
this._offset += this._value;
|
|
|
|
this._value = 0;
|
|
|
|
if (this.__isNative) {
|
|
|
|
NativeAnimatedAPI.extractAnimatedNodeOffset(this.__getNativeTag());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-04 02:29:04 +00:00
|
|
|
/**
|
|
|
|
* Adds an asynchronous listener to the value so you can observe updates from
|
2016-01-06 18:32:13 +00:00
|
|
|
* animations. This is useful because there is no way to
|
2015-12-15 17:08:39 +00:00
|
|
|
* synchronously read the value because it might be driven natively.
|
2015-09-04 02:29:04 +00:00
|
|
|
*/
|
2015-07-07 20:34:09 +00:00
|
|
|
addListener(callback: ValueListenerCallback): string {
|
|
|
|
var id = String(_uniqueId++);
|
|
|
|
this._listeners[id] = callback;
|
2016-08-04 20:11:37 +00:00
|
|
|
if (this.__isNative) {
|
|
|
|
this._startListeningToNativeValueUpdates();
|
|
|
|
}
|
2015-07-07 20:34:09 +00:00
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
|
|
|
removeListener(id: string): void {
|
|
|
|
delete this._listeners[id];
|
2016-08-04 20:11:37 +00:00
|
|
|
if (this.__isNative && Object.keys(this._listeners).length === 0) {
|
|
|
|
this._stopListeningForNativeValueUpdates();
|
|
|
|
}
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
removeAllListeners(): void {
|
|
|
|
this._listeners = {};
|
2016-08-04 20:11:37 +00:00
|
|
|
if (this.__isNative) {
|
|
|
|
this._stopListeningForNativeValueUpdates();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_startListeningToNativeValueUpdates() {
|
2016-08-12 01:10:16 +00:00
|
|
|
if (this.__nativeAnimatedValueListener) {
|
2016-08-04 20:11:37 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
NativeAnimatedAPI.startListeningToAnimatedNodeValue(this.__getNativeTag());
|
2016-08-12 01:10:16 +00:00
|
|
|
this.__nativeAnimatedValueListener = NativeAnimatedHelper.nativeEventEmitter.addListener(
|
2016-08-11 11:18:45 +00:00
|
|
|
'onAnimatedValueUpdate',
|
|
|
|
(data) => {
|
|
|
|
if (data.tag !== this.__getNativeTag()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this._updateValue(data.value, false /* flush */);
|
2016-08-04 20:11:37 +00:00
|
|
|
}
|
2016-08-11 11:18:45 +00:00
|
|
|
);
|
2016-08-04 20:11:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
_stopListeningForNativeValueUpdates() {
|
2016-08-12 01:10:16 +00:00
|
|
|
if (!this.__nativeAnimatedValueListener) {
|
2016-08-04 20:11:37 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.__nativeAnimatedValueListener.remove();
|
2016-09-04 22:51:59 +00:00
|
|
|
this.__nativeAnimatedValueListener = null;
|
2016-08-04 20:11:37 +00:00
|
|
|
NativeAnimatedAPI.stopListeningToAnimatedNodeValue(this.__getNativeTag());
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
|
2015-09-04 02:29:04 +00:00
|
|
|
/**
|
|
|
|
* 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());
|
|
|
|
}
|
|
|
|
|
Adds Animated.loop to Animated API
Summary:
* Any animation can be looped on the javascript thread
* Only basic animations supported natively at this stage, loops run
using the native driver cannot contain animations of type sequence,
parallel, stagger, or loop
Motivation: We need a spinner in our app that is displayed and animated while the javascript thread is tied up with other tasks. This means it needs to be offloaded from the javascript thread, so that it will continue to run while those tasks are churning away.
I originally submitted PR #9513, which has served our needs, but brentvatne pointed out a better way to do it. Had hoped his suggestion would be implemented by janicduplessis or another fb employee, but after 5 months I thought I'd give it another push.
I've put together an implementation that basically matches the suggested API. Let me know what you think, and whether others can pick it up from here and get it in to core.
Personal Motivation: I am leaving my current organisation on Feb 10th, so am trying to clean thing
Closes https://github.com/facebook/react-native/pull/11973
Differential Revision: D4704381
fbshipit-source-id: 42a2cdf5d53a7c0d08f86a58485f7f38739e6cd9
2017-03-14 06:49:47 +00:00
|
|
|
/**
|
|
|
|
* Stops any animation and resets the value to its original
|
|
|
|
*/
|
|
|
|
resetAnimation(callback?: ?(value: number) => void): void {
|
|
|
|
this.stopAnimation(callback);
|
|
|
|
this._value = this._startingValue;
|
|
|
|
}
|
|
|
|
|
2015-09-04 02:29:04 +00:00
|
|
|
/**
|
|
|
|
* Interpolates the value before updating the property, e.g. mapping 0-1 to
|
|
|
|
* 0-10.
|
|
|
|
*/
|
|
|
|
interpolate(config: InterpolationConfigType): AnimatedInterpolation {
|
2016-04-23 09:37:01 +00:00
|
|
|
return new AnimatedInterpolation(this, config);
|
2015-09-04 02:29:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Typically only used internally, but could be used by a custom Animation
|
|
|
|
* class.
|
|
|
|
*/
|
2015-07-07 20:34:09 +00:00
|
|
|
animate(animation: Animation, callback: ?EndCallback): void {
|
2015-10-15 15:15:28 +00:00
|
|
|
var handle = null;
|
|
|
|
if (animation.__isInteraction) {
|
|
|
|
handle = InteractionManager.createInteractionHandle();
|
|
|
|
}
|
2015-07-07 20:34:09 +00:00
|
|
|
var previousAnimation = this._animation;
|
|
|
|
this._animation && this._animation.stop();
|
|
|
|
this._animation = animation;
|
|
|
|
animation.start(
|
|
|
|
this._value,
|
|
|
|
(value) => {
|
2016-08-11 11:18:45 +00:00
|
|
|
// Natively driven animations will never call into that callback, therefore we can always
|
|
|
|
// pass flush = true to allow the updated value to propagate to native with setNativeProps
|
2016-05-04 09:47:47 +00:00
|
|
|
this._updateValue(value, true /* flush */);
|
2015-07-07 20:34:09 +00:00
|
|
|
},
|
|
|
|
(result) => {
|
|
|
|
this._animation = null;
|
2015-10-15 15:15:28 +00:00
|
|
|
if (handle !== null) {
|
|
|
|
InteractionManager.clearInteractionHandle(handle);
|
|
|
|
}
|
2015-07-07 20:34:09 +00:00
|
|
|
callback && callback(result);
|
|
|
|
},
|
|
|
|
previousAnimation,
|
2016-03-24 13:18:39 +00:00
|
|
|
this
|
2015-07-07 20:34:09 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2015-09-04 02:29:04 +00:00
|
|
|
/**
|
|
|
|
* Typically only used internally.
|
|
|
|
*/
|
2015-07-07 20:34:09 +00:00
|
|
|
stopTracking(): void {
|
2015-08-20 03:06:22 +00:00
|
|
|
this._tracking && this._tracking.__detach();
|
2015-07-07 20:34:09 +00:00
|
|
|
this._tracking = null;
|
|
|
|
}
|
|
|
|
|
2015-09-04 02:29:04 +00:00
|
|
|
/**
|
|
|
|
* Typically only used internally.
|
|
|
|
*/
|
2015-07-07 20:34:09 +00:00
|
|
|
track(tracking: Animated): void {
|
|
|
|
this.stopTracking();
|
|
|
|
this._tracking = tracking;
|
|
|
|
}
|
|
|
|
|
2016-05-04 09:47:47 +00:00
|
|
|
_updateValue(value: number, flush: bool): void {
|
2015-07-07 20:34:09 +00:00
|
|
|
this._value = value;
|
2016-05-04 09:47:47 +00:00
|
|
|
if (flush) {
|
|
|
|
_flush(this);
|
|
|
|
}
|
2015-07-07 20:34:09 +00:00
|
|
|
for (var key in this._listeners) {
|
|
|
|
this._listeners[key]({value: this.__getValue()});
|
|
|
|
}
|
|
|
|
}
|
2016-03-24 13:18:39 +00:00
|
|
|
|
|
|
|
__getNativeConfig(): Object {
|
|
|
|
return {
|
|
|
|
type: 'value',
|
2016-11-01 03:39:44 +00:00
|
|
|
value: this._value,
|
|
|
|
offset: this._offset,
|
2016-03-24 13:18:39 +00:00
|
|
|
};
|
|
|
|
}
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
|
2016-08-09 11:20:33 +00:00
|
|
|
type ValueXYListenerCallback = (value: {x: number, y: number}) => void;
|
2015-09-04 02:29:04 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 2D Value for driving 2D animations, such as pan gestures. Almost identical
|
|
|
|
* API to normal `Animated.Value`, but multiplexed. Contains two regular
|
2017-02-17 22:41:23 +00:00
|
|
|
* `Animated.Value`s under the hood.
|
|
|
|
*
|
|
|
|
* #### Example
|
2015-09-04 02:29:04 +00:00
|
|
|
*
|
|
|
|
*```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 (
|
|
|
|
* <Animated.View
|
|
|
|
* {...this.state.panResponder.panHandlers}
|
|
|
|
* style={this.state.pan.getLayout()}>
|
|
|
|
* {this.props.children}
|
|
|
|
* </Animated.View>
|
|
|
|
* );
|
|
|
|
* }
|
|
|
|
* }
|
|
|
|
*```
|
|
|
|
*/
|
2015-07-07 20:34:09 +00:00
|
|
|
class AnimatedValueXY extends AnimatedWithChildren {
|
|
|
|
x: AnimatedValue;
|
|
|
|
y: AnimatedValue;
|
2016-08-09 11:20:33 +00:00
|
|
|
_listeners: {[key: string]: {x: string, y: string}};
|
2015-07-07 20:34:09 +00:00
|
|
|
|
2016-08-09 11:20:33 +00:00
|
|
|
constructor(valueIn?: ?{x: number | AnimatedValue, y: number | AnimatedValue}) {
|
2015-07-07 20:34:09 +00:00
|
|
|
super();
|
|
|
|
var value: any = valueIn || {x: 0, y: 0}; // @flowfixme: shouldn't need `: any`
|
|
|
|
if (typeof value.x === 'number' && typeof value.y === 'number') {
|
|
|
|
this.x = new AnimatedValue(value.x);
|
|
|
|
this.y = new AnimatedValue(value.y);
|
|
|
|
} else {
|
|
|
|
invariant(
|
|
|
|
value.x instanceof AnimatedValue &&
|
|
|
|
value.y instanceof AnimatedValue,
|
|
|
|
'AnimatedValueXY must be initalized with an object of numbers or ' +
|
|
|
|
'AnimatedValues.'
|
|
|
|
);
|
|
|
|
this.x = value.x;
|
|
|
|
this.y = value.y;
|
|
|
|
}
|
|
|
|
this._listeners = {};
|
|
|
|
}
|
|
|
|
|
2016-08-09 11:20:33 +00:00
|
|
|
setValue(value: {x: number, y: number}) {
|
2015-07-07 20:34:09 +00:00
|
|
|
this.x.setValue(value.x);
|
|
|
|
this.y.setValue(value.y);
|
|
|
|
}
|
|
|
|
|
2016-08-09 11:20:33 +00:00
|
|
|
setOffset(offset: {x: number, y: number}) {
|
2015-07-07 20:34:09 +00:00
|
|
|
this.x.setOffset(offset.x);
|
|
|
|
this.y.setOffset(offset.y);
|
|
|
|
}
|
|
|
|
|
|
|
|
flattenOffset(): void {
|
|
|
|
this.x.flattenOffset();
|
|
|
|
this.y.flattenOffset();
|
|
|
|
}
|
|
|
|
|
2017-02-03 20:05:45 +00:00
|
|
|
extractOffset(): void {
|
|
|
|
this.x.extractOffset();
|
|
|
|
this.y.extractOffset();
|
|
|
|
}
|
|
|
|
|
2016-08-09 11:20:33 +00:00
|
|
|
__getValue(): {x: number, y: number} {
|
2015-07-07 20:34:09 +00:00
|
|
|
return {
|
|
|
|
x: this.x.__getValue(),
|
|
|
|
y: this.y.__getValue(),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
Adds Animated.loop to Animated API
Summary:
* Any animation can be looped on the javascript thread
* Only basic animations supported natively at this stage, loops run
using the native driver cannot contain animations of type sequence,
parallel, stagger, or loop
Motivation: We need a spinner in our app that is displayed and animated while the javascript thread is tied up with other tasks. This means it needs to be offloaded from the javascript thread, so that it will continue to run while those tasks are churning away.
I originally submitted PR #9513, which has served our needs, but brentvatne pointed out a better way to do it. Had hoped his suggestion would be implemented by janicduplessis or another fb employee, but after 5 months I thought I'd give it another push.
I've put together an implementation that basically matches the suggested API. Let me know what you think, and whether others can pick it up from here and get it in to core.
Personal Motivation: I am leaving my current organisation on Feb 10th, so am trying to clean thing
Closes https://github.com/facebook/react-native/pull/11973
Differential Revision: D4704381
fbshipit-source-id: 42a2cdf5d53a7c0d08f86a58485f7f38739e6cd9
2017-03-14 06:49:47 +00:00
|
|
|
resetAnimation(callback?: (value: {x: number, y: number}) => void): void {
|
|
|
|
this.x.resetAnimation();
|
|
|
|
this.y.resetAnimation();
|
|
|
|
callback && callback(this.__getValue());
|
|
|
|
}
|
|
|
|
|
2016-09-08 11:40:57 +00:00
|
|
|
stopAnimation(callback?: (value: {x: number, y: number}) => void): void {
|
2015-07-07 20:34:09 +00:00
|
|
|
this.x.stopAnimation();
|
|
|
|
this.y.stopAnimation();
|
|
|
|
callback && callback(this.__getValue());
|
|
|
|
}
|
|
|
|
|
|
|
|
addListener(callback: ValueXYListenerCallback): string {
|
|
|
|
var id = String(_uniqueId++);
|
|
|
|
var jointCallback = ({value: number}) => {
|
|
|
|
callback(this.__getValue());
|
|
|
|
};
|
|
|
|
this._listeners[id] = {
|
|
|
|
x: this.x.addListener(jointCallback),
|
|
|
|
y: this.y.addListener(jointCallback),
|
|
|
|
};
|
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
|
|
|
removeListener(id: string): void {
|
|
|
|
this.x.removeListener(this._listeners[id].x);
|
|
|
|
this.y.removeListener(this._listeners[id].y);
|
|
|
|
delete this._listeners[id];
|
|
|
|
}
|
|
|
|
|
2017-01-14 22:39:11 +00:00
|
|
|
removeAllListeners(): void {
|
|
|
|
this.x.removeAllListeners();
|
|
|
|
this.y.removeAllListeners();
|
|
|
|
this._listeners = {};
|
|
|
|
}
|
|
|
|
|
2015-09-04 02:29:04 +00:00
|
|
|
/**
|
|
|
|
* Converts `{x, y}` into `{left, top}` for use in style, e.g.
|
|
|
|
*
|
|
|
|
*```javascript
|
|
|
|
* style={this.state.anim.getLayout()}
|
|
|
|
*```
|
|
|
|
*/
|
2015-07-07 20:34:09 +00:00
|
|
|
getLayout(): {[key: string]: AnimatedValue} {
|
|
|
|
return {
|
|
|
|
left: this.x,
|
|
|
|
top: this.y,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2015-09-04 02:29:04 +00:00
|
|
|
/**
|
|
|
|
* Converts `{x, y}` into a useable translation transform, e.g.
|
|
|
|
*
|
|
|
|
*```javascript
|
|
|
|
* style={{
|
|
|
|
* transform: this.state.anim.getTranslateTransform()
|
|
|
|
* }}
|
|
|
|
*```
|
|
|
|
*/
|
2015-07-07 20:34:09 +00:00
|
|
|
getTranslateTransform(): Array<{[key: string]: AnimatedValue}> {
|
|
|
|
return [
|
|
|
|
{translateX: this.x},
|
|
|
|
{translateY: this.y}
|
|
|
|
];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class AnimatedInterpolation extends AnimatedWithChildren {
|
|
|
|
_parent: Animated;
|
2016-04-23 09:37:01 +00:00
|
|
|
_config: InterpolationConfigType;
|
2015-07-07 20:34:09 +00:00
|
|
|
_interpolation: (input: number) => number | string;
|
|
|
|
|
2016-04-23 09:37:01 +00:00
|
|
|
constructor(parent: Animated, config: InterpolationConfigType) {
|
2015-07-07 20:34:09 +00:00
|
|
|
super();
|
|
|
|
this._parent = parent;
|
2016-04-23 09:37:01 +00:00
|
|
|
this._config = config;
|
|
|
|
this._interpolation = Interpolation.create(config);
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
__getValue(): number | string {
|
|
|
|
var parentValue: number = this._parent.__getValue();
|
|
|
|
invariant(
|
|
|
|
typeof parentValue === 'number',
|
|
|
|
'Cannot interpolate an input which is not a number.'
|
|
|
|
);
|
|
|
|
return this._interpolation(parentValue);
|
|
|
|
}
|
|
|
|
|
|
|
|
interpolate(config: InterpolationConfigType): AnimatedInterpolation {
|
2016-04-23 09:37:01 +00:00
|
|
|
return new AnimatedInterpolation(this, config);
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
|
2015-08-20 03:06:22 +00:00
|
|
|
__attach(): void {
|
|
|
|
this._parent.__addChild(this);
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
|
2015-08-20 03:06:22 +00:00
|
|
|
__detach(): void {
|
|
|
|
this._parent.__removeChild(this);
|
2016-04-23 09:37:01 +00:00
|
|
|
super.__detach();
|
|
|
|
}
|
|
|
|
|
2017-02-09 22:32:32 +00:00
|
|
|
__transformDataType(range: Array<any>) {
|
2016-08-05 09:52:30 +00:00
|
|
|
// Change the string array type to number array
|
|
|
|
// So we can reuse the same logic in iOS and Android platform
|
|
|
|
return range.map(function (value) {
|
|
|
|
if (typeof value !== 'string') {
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
if (/deg$/.test(value)) {
|
2016-08-09 13:32:41 +00:00
|
|
|
const degrees = parseFloat(value, 10) || 0;
|
|
|
|
const radians = degrees * Math.PI / 180.0;
|
2016-08-05 09:52:30 +00:00
|
|
|
return radians;
|
|
|
|
} else {
|
|
|
|
// Assume radians
|
|
|
|
return parseFloat(value, 10) || 0;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-04-23 09:37:01 +00:00
|
|
|
__getNativeConfig(): any {
|
2016-09-06 22:16:16 +00:00
|
|
|
if (__DEV__) {
|
|
|
|
NativeAnimatedHelper.validateInterpolation(this._config);
|
|
|
|
}
|
|
|
|
|
2016-04-23 09:37:01 +00:00
|
|
|
return {
|
2016-09-06 22:16:16 +00:00
|
|
|
inputRange: this._config.inputRange,
|
2016-08-05 09:52:30 +00:00
|
|
|
// Only the `outputRange` can contain strings so we don't need to tranform `inputRange` here
|
|
|
|
outputRange: this.__transformDataType(this._config.outputRange),
|
2016-09-06 22:16:16 +00:00
|
|
|
extrapolateLeft: this._config.extrapolateLeft || this._config.extrapolate || 'extend',
|
|
|
|
extrapolateRight: this._config.extrapolateRight || this._config.extrapolate || 'extend',
|
2016-04-23 09:37:01 +00:00
|
|
|
type: 'interpolation',
|
|
|
|
};
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-10 21:27:52 +00:00
|
|
|
class AnimatedAddition extends AnimatedWithChildren {
|
|
|
|
_a: Animated;
|
|
|
|
_b: Animated;
|
|
|
|
|
2016-02-26 00:59:22 +00:00
|
|
|
constructor(a: Animated | number, b: Animated | number) {
|
2015-12-10 21:27:52 +00:00
|
|
|
super();
|
2016-02-26 00:59:22 +00:00
|
|
|
this._a = typeof a === 'number' ? new AnimatedValue(a) : a;
|
|
|
|
this._b = typeof b === 'number' ? new AnimatedValue(b) : b;
|
2015-12-10 21:27:52 +00:00
|
|
|
}
|
|
|
|
|
2016-04-19 09:57:15 +00:00
|
|
|
__makeNative() {
|
|
|
|
this._a.__makeNative();
|
|
|
|
this._b.__makeNative();
|
2016-04-23 09:37:01 +00:00
|
|
|
super.__makeNative();
|
2016-04-19 09:57:15 +00:00
|
|
|
}
|
|
|
|
|
2015-12-10 21:27:52 +00:00
|
|
|
__getValue(): number {
|
|
|
|
return this._a.__getValue() + this._b.__getValue();
|
|
|
|
}
|
|
|
|
|
|
|
|
interpolate(config: InterpolationConfigType): AnimatedInterpolation {
|
2016-04-23 09:37:01 +00:00
|
|
|
return new AnimatedInterpolation(this, config);
|
2015-12-10 21:27:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
__attach(): void {
|
|
|
|
this._a.__addChild(this);
|
|
|
|
this._b.__addChild(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
__detach(): void {
|
|
|
|
this._a.__removeChild(this);
|
|
|
|
this._b.__removeChild(this);
|
2016-04-19 09:57:15 +00:00
|
|
|
super.__detach();
|
|
|
|
}
|
|
|
|
|
|
|
|
__getNativeConfig(): any {
|
|
|
|
return {
|
|
|
|
type: 'addition',
|
|
|
|
input: [this._a.__getNativeTag(), this._b.__getNativeTag()],
|
|
|
|
};
|
2015-12-10 21:27:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-26 23:32:27 +00:00
|
|
|
class AnimatedDivision extends AnimatedWithChildren {
|
|
|
|
_a: Animated;
|
|
|
|
_b: Animated;
|
|
|
|
|
|
|
|
constructor(a: Animated | number, b: Animated | number) {
|
|
|
|
super();
|
|
|
|
this._a = typeof a === 'number' ? new AnimatedValue(a) : a;
|
|
|
|
this._b = typeof b === 'number' ? new AnimatedValue(b) : b;
|
|
|
|
}
|
|
|
|
|
|
|
|
__makeNative() {
|
|
|
|
super.__makeNative();
|
|
|
|
this._a.__makeNative();
|
|
|
|
this._b.__makeNative();
|
|
|
|
}
|
|
|
|
|
|
|
|
__getValue(): number {
|
|
|
|
const a = this._a.__getValue();
|
|
|
|
const b = this._b.__getValue();
|
|
|
|
if (b === 0) {
|
|
|
|
console.error('Detected division by zero in AnimatedDivision');
|
|
|
|
}
|
|
|
|
return a / b;
|
|
|
|
}
|
|
|
|
|
|
|
|
interpolate(config: InterpolationConfigType): AnimatedInterpolation {
|
|
|
|
return new AnimatedInterpolation(this, config);
|
|
|
|
}
|
|
|
|
|
|
|
|
__attach(): void {
|
|
|
|
this._a.__addChild(this);
|
|
|
|
this._b.__addChild(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
__detach(): void {
|
|
|
|
this._a.__removeChild(this);
|
|
|
|
this._b.__removeChild(this);
|
|
|
|
super.__detach();
|
|
|
|
}
|
|
|
|
|
|
|
|
__getNativeConfig(): any {
|
|
|
|
return {
|
|
|
|
type: 'division',
|
|
|
|
input: [this._a.__getNativeTag(), this._b.__getNativeTag()],
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-10 21:27:52 +00:00
|
|
|
class AnimatedMultiplication extends AnimatedWithChildren {
|
|
|
|
_a: Animated;
|
|
|
|
_b: Animated;
|
|
|
|
|
2016-02-26 00:59:22 +00:00
|
|
|
constructor(a: Animated | number, b: Animated | number) {
|
2015-12-10 21:27:52 +00:00
|
|
|
super();
|
2016-02-26 00:59:22 +00:00
|
|
|
this._a = typeof a === 'number' ? new AnimatedValue(a) : a;
|
|
|
|
this._b = typeof b === 'number' ? new AnimatedValue(b) : b;
|
2015-12-10 21:27:52 +00:00
|
|
|
}
|
|
|
|
|
2016-04-19 20:19:27 +00:00
|
|
|
__makeNative() {
|
|
|
|
super.__makeNative();
|
|
|
|
this._a.__makeNative();
|
|
|
|
this._b.__makeNative();
|
|
|
|
}
|
|
|
|
|
2015-12-10 21:27:52 +00:00
|
|
|
__getValue(): number {
|
|
|
|
return this._a.__getValue() * this._b.__getValue();
|
|
|
|
}
|
|
|
|
|
|
|
|
interpolate(config: InterpolationConfigType): AnimatedInterpolation {
|
2016-04-23 09:37:01 +00:00
|
|
|
return new AnimatedInterpolation(this, config);
|
2015-12-10 21:27:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
__attach(): void {
|
|
|
|
this._a.__addChild(this);
|
|
|
|
this._b.__addChild(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
__detach(): void {
|
|
|
|
this._a.__removeChild(this);
|
|
|
|
this._b.__removeChild(this);
|
2016-04-19 20:19:27 +00:00
|
|
|
super.__detach();
|
|
|
|
}
|
|
|
|
|
|
|
|
__getNativeConfig(): any {
|
|
|
|
return {
|
|
|
|
type: 'multiplication',
|
|
|
|
input: [this._a.__getNativeTag(), this._b.__getNativeTag()],
|
|
|
|
};
|
2015-12-10 21:27:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-26 01:18:27 +00:00
|
|
|
class AnimatedModulo extends AnimatedWithChildren {
|
|
|
|
_a: Animated;
|
|
|
|
_modulus: number;
|
|
|
|
|
|
|
|
constructor(a: Animated, modulus: number) {
|
|
|
|
super();
|
|
|
|
this._a = a;
|
|
|
|
this._modulus = modulus;
|
|
|
|
}
|
|
|
|
|
2016-08-31 21:38:53 +00:00
|
|
|
__makeNative() {
|
|
|
|
super.__makeNative();
|
|
|
|
this._a.__makeNative();
|
|
|
|
}
|
|
|
|
|
2016-02-26 01:18:27 +00:00
|
|
|
__getValue(): number {
|
|
|
|
return (this._a.__getValue() % this._modulus + this._modulus) % this._modulus;
|
|
|
|
}
|
|
|
|
|
|
|
|
interpolate(config: InterpolationConfigType): AnimatedInterpolation {
|
2016-04-23 09:37:01 +00:00
|
|
|
return new AnimatedInterpolation(this, config);
|
2016-02-26 01:18:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
__attach(): void {
|
|
|
|
this._a.__addChild(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
__detach(): void {
|
|
|
|
this._a.__removeChild(this);
|
2017-03-29 10:59:58 +00:00
|
|
|
super.__detach();
|
2016-02-26 01:18:27 +00:00
|
|
|
}
|
2016-08-31 21:38:53 +00:00
|
|
|
|
|
|
|
__getNativeConfig(): any {
|
|
|
|
return {
|
|
|
|
type: 'modulus',
|
|
|
|
input: this._a.__getNativeTag(),
|
|
|
|
modulus: this._modulus,
|
|
|
|
};
|
|
|
|
}
|
2016-02-26 01:18:27 +00:00
|
|
|
}
|
|
|
|
|
2016-08-23 20:59:07 +00:00
|
|
|
class AnimatedDiffClamp extends AnimatedWithChildren {
|
|
|
|
_a: Animated;
|
|
|
|
_min: number;
|
|
|
|
_max: number;
|
|
|
|
_value: number;
|
|
|
|
_lastValue: number;
|
|
|
|
|
|
|
|
constructor(a: Animated, min: number, max: number) {
|
|
|
|
super();
|
|
|
|
|
|
|
|
this._a = a;
|
|
|
|
this._min = min;
|
|
|
|
this._max = max;
|
|
|
|
this._value = this._lastValue = this._a.__getValue();
|
|
|
|
}
|
|
|
|
|
2016-09-06 22:30:17 +00:00
|
|
|
__makeNative() {
|
|
|
|
super.__makeNative();
|
|
|
|
this._a.__makeNative();
|
|
|
|
}
|
|
|
|
|
2016-08-23 20:59:07 +00:00
|
|
|
interpolate(config: InterpolationConfigType): AnimatedInterpolation {
|
|
|
|
return new AnimatedInterpolation(this, config);
|
|
|
|
}
|
|
|
|
|
|
|
|
__getValue(): number {
|
|
|
|
const value = this._a.__getValue();
|
|
|
|
const diff = value - this._lastValue;
|
|
|
|
this._lastValue = value;
|
|
|
|
this._value = Math.min(Math.max(this._value + diff, this._min), this._max);
|
|
|
|
return this._value;
|
|
|
|
}
|
|
|
|
|
|
|
|
__attach(): void {
|
|
|
|
this._a.__addChild(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
__detach(): void {
|
|
|
|
this._a.__removeChild(this);
|
2017-03-29 10:59:58 +00:00
|
|
|
super.__detach();
|
2016-08-23 20:59:07 +00:00
|
|
|
}
|
2016-09-06 22:30:17 +00:00
|
|
|
|
|
|
|
__getNativeConfig(): any {
|
|
|
|
return {
|
|
|
|
type: 'diffclamp',
|
|
|
|
input: this._a.__getNativeTag(),
|
|
|
|
min: this._min,
|
|
|
|
max: this._max,
|
|
|
|
};
|
|
|
|
}
|
2016-08-23 20:59:07 +00:00
|
|
|
}
|
|
|
|
|
2015-07-07 20:34:09 +00:00
|
|
|
class AnimatedTransform extends AnimatedWithChildren {
|
|
|
|
_transforms: Array<Object>;
|
|
|
|
|
|
|
|
constructor(transforms: Array<Object>) {
|
|
|
|
super();
|
|
|
|
this._transforms = transforms;
|
|
|
|
}
|
|
|
|
|
2016-06-09 17:34:41 +00:00
|
|
|
__makeNative() {
|
|
|
|
super.__makeNative();
|
|
|
|
this._transforms.forEach(transform => {
|
|
|
|
for (var key in transform) {
|
|
|
|
var value = transform[key];
|
|
|
|
if (value instanceof Animated) {
|
|
|
|
value.__makeNative();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-07-07 20:34:09 +00:00
|
|
|
__getValue(): Array<Object> {
|
|
|
|
return this._transforms.map(transform => {
|
|
|
|
var result = {};
|
|
|
|
for (var key in transform) {
|
|
|
|
var value = transform[key];
|
|
|
|
if (value instanceof Animated) {
|
|
|
|
result[key] = value.__getValue();
|
|
|
|
} else {
|
|
|
|
result[key] = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-08-20 03:06:22 +00:00
|
|
|
__getAnimatedValue(): Array<Object> {
|
2015-07-07 20:34:09 +00:00
|
|
|
return this._transforms.map(transform => {
|
|
|
|
var result = {};
|
|
|
|
for (var key in transform) {
|
|
|
|
var value = transform[key];
|
|
|
|
if (value instanceof Animated) {
|
2015-08-20 03:06:22 +00:00
|
|
|
result[key] = value.__getAnimatedValue();
|
2015-07-07 20:34:09 +00:00
|
|
|
} else {
|
|
|
|
// All transform components needed to recompose matrix
|
|
|
|
result[key] = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-08-20 03:06:22 +00:00
|
|
|
__attach(): void {
|
2015-07-07 20:34:09 +00:00
|
|
|
this._transforms.forEach(transform => {
|
|
|
|
for (var key in transform) {
|
|
|
|
var value = transform[key];
|
|
|
|
if (value instanceof Animated) {
|
2015-08-20 03:06:22 +00:00
|
|
|
value.__addChild(this);
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-08-20 03:06:22 +00:00
|
|
|
__detach(): void {
|
2015-07-07 20:34:09 +00:00
|
|
|
this._transforms.forEach(transform => {
|
|
|
|
for (var key in transform) {
|
|
|
|
var value = transform[key];
|
|
|
|
if (value instanceof Animated) {
|
2015-08-20 03:06:22 +00:00
|
|
|
value.__removeChild(this);
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2017-03-29 10:59:58 +00:00
|
|
|
super.__detach();
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
2016-06-09 17:34:41 +00:00
|
|
|
|
|
|
|
__getNativeConfig(): any {
|
2016-08-02 21:23:18 +00:00
|
|
|
var transConfigs = [];
|
2016-06-09 17:34:41 +00:00
|
|
|
|
|
|
|
this._transforms.forEach(transform => {
|
|
|
|
for (var key in transform) {
|
|
|
|
var value = transform[key];
|
|
|
|
if (value instanceof Animated) {
|
2016-08-02 21:23:18 +00:00
|
|
|
transConfigs.push({
|
2016-08-07 07:44:09 +00:00
|
|
|
type: 'animated',
|
2016-08-02 21:23:18 +00:00
|
|
|
property: key,
|
|
|
|
nodeTag: value.__getNativeTag(),
|
|
|
|
});
|
2016-08-07 07:44:09 +00:00
|
|
|
} else {
|
|
|
|
transConfigs.push({
|
|
|
|
type: 'static',
|
|
|
|
property: key,
|
|
|
|
value,
|
|
|
|
});
|
2016-06-09 17:34:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2016-08-02 21:23:18 +00:00
|
|
|
NativeAnimatedHelper.validateTransform(transConfigs);
|
2016-06-09 17:34:41 +00:00
|
|
|
return {
|
|
|
|
type: 'transform',
|
2016-08-02 21:23:18 +00:00
|
|
|
transforms: transConfigs,
|
2016-06-09 17:34:41 +00:00
|
|
|
};
|
|
|
|
}
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
class AnimatedStyle extends AnimatedWithChildren {
|
|
|
|
_style: Object;
|
|
|
|
|
|
|
|
constructor(style: any) {
|
|
|
|
super();
|
|
|
|
style = flattenStyle(style) || {};
|
|
|
|
if (style.transform) {
|
|
|
|
style = {
|
|
|
|
...style,
|
|
|
|
transform: new AnimatedTransform(style.transform),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
this._style = style;
|
|
|
|
}
|
|
|
|
|
2017-03-16 21:44:23 +00:00
|
|
|
// Recursively get values for nested styles (like iOS's shadowOffset)
|
|
|
|
__walkStyleAndGetValues(style) {
|
2017-03-18 05:02:04 +00:00
|
|
|
const updatedStyle = {};
|
|
|
|
for (const key in style) {
|
|
|
|
const value = style[key];
|
2015-07-07 20:34:09 +00:00
|
|
|
if (value instanceof Animated) {
|
2016-03-24 13:18:39 +00:00
|
|
|
if (!value.__isNative) {
|
2016-08-11 11:18:45 +00:00
|
|
|
// We cannot use value of natively driven nodes this way as the value we have access from
|
|
|
|
// JS may not be up to date.
|
2017-03-16 21:44:23 +00:00
|
|
|
updatedStyle[key] = value.__getValue();
|
2016-03-24 13:18:39 +00:00
|
|
|
}
|
2017-03-16 21:44:23 +00:00
|
|
|
} else if (value && !Array.isArray(value) && typeof value === 'object') {
|
|
|
|
// Support animating nested values (for example: shadowOffset.height)
|
|
|
|
updatedStyle[key] = this.__walkStyleAndGetValues(value);
|
2015-07-07 20:34:09 +00:00
|
|
|
} else {
|
2017-03-16 21:44:23 +00:00
|
|
|
updatedStyle[key] = value;
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
}
|
2017-03-16 21:44:23 +00:00
|
|
|
return updatedStyle;
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
|
2017-03-16 21:44:23 +00:00
|
|
|
__getValue(): Object {
|
|
|
|
return this.__walkStyleAndGetValues(this._style);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Recursively get animated values for nested styles (like iOS's shadowOffset)
|
|
|
|
__walkStyleAndGetAnimatedValues(style) {
|
2017-03-18 05:02:04 +00:00
|
|
|
const updatedStyle = {};
|
|
|
|
for (const key in style) {
|
|
|
|
const value = style[key];
|
2015-07-07 20:34:09 +00:00
|
|
|
if (value instanceof Animated) {
|
2017-03-16 21:44:23 +00:00
|
|
|
updatedStyle[key] = value.__getAnimatedValue();
|
|
|
|
} else if (value && !Array.isArray(value) && typeof value === 'object') {
|
|
|
|
// Support animating nested values (for example: shadowOffset.height)
|
|
|
|
updatedStyle[key] = this.__walkStyleAndGetAnimatedValues(value);
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
}
|
2017-03-16 21:44:23 +00:00
|
|
|
return updatedStyle;
|
|
|
|
}
|
|
|
|
|
|
|
|
__getAnimatedValue(): Object {
|
|
|
|
return this.__walkStyleAndGetAnimatedValues(this._style);
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
|
2015-08-20 03:06:22 +00:00
|
|
|
__attach(): void {
|
2015-07-07 20:34:09 +00:00
|
|
|
for (var key in this._style) {
|
|
|
|
var value = this._style[key];
|
|
|
|
if (value instanceof Animated) {
|
2015-08-20 03:06:22 +00:00
|
|
|
value.__addChild(this);
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-20 03:06:22 +00:00
|
|
|
__detach(): void {
|
2015-07-07 20:34:09 +00:00
|
|
|
for (var key in this._style) {
|
|
|
|
var value = this._style[key];
|
|
|
|
if (value instanceof Animated) {
|
2015-08-20 03:06:22 +00:00
|
|
|
value.__removeChild(this);
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
}
|
2017-03-29 10:59:58 +00:00
|
|
|
super.__detach();
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
2016-03-24 13:18:39 +00:00
|
|
|
|
|
|
|
__makeNative() {
|
|
|
|
super.__makeNative();
|
|
|
|
for (var key in this._style) {
|
|
|
|
var value = this._style[key];
|
|
|
|
if (value instanceof Animated) {
|
|
|
|
value.__makeNative();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
__getNativeConfig(): Object {
|
|
|
|
var styleConfig = {};
|
2016-08-09 13:32:41 +00:00
|
|
|
for (const styleKey in this._style) {
|
2016-03-24 13:18:39 +00:00
|
|
|
if (this._style[styleKey] instanceof Animated) {
|
|
|
|
styleConfig[styleKey] = this._style[styleKey].__getNativeTag();
|
|
|
|
}
|
|
|
|
// Non-animated styles are set using `setNativeProps`, no need
|
|
|
|
// to pass those as a part of the node config
|
|
|
|
}
|
|
|
|
NativeAnimatedHelper.validateStyles(styleConfig);
|
|
|
|
return {
|
|
|
|
type: 'style',
|
|
|
|
style: styleConfig,
|
|
|
|
};
|
|
|
|
}
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
class AnimatedProps extends Animated {
|
|
|
|
_props: Object;
|
2016-03-24 13:18:39 +00:00
|
|
|
_animatedView: any;
|
2015-07-07 20:34:09 +00:00
|
|
|
_callback: () => void;
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
props: Object,
|
|
|
|
callback: () => void,
|
|
|
|
) {
|
|
|
|
super();
|
|
|
|
if (props.style) {
|
|
|
|
props = {
|
|
|
|
...props,
|
|
|
|
style: new AnimatedStyle(props.style),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
this._props = props;
|
|
|
|
this._callback = callback;
|
2015-08-20 03:06:22 +00:00
|
|
|
this.__attach();
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
__getValue(): Object {
|
|
|
|
var props = {};
|
|
|
|
for (var key in this._props) {
|
|
|
|
var value = this._props[key];
|
|
|
|
if (value instanceof Animated) {
|
2016-08-04 20:11:37 +00:00
|
|
|
if (!value.__isNative || value instanceof AnimatedStyle) {
|
2016-08-11 11:18:45 +00:00
|
|
|
// We cannot use value of natively driven nodes this way as the value we have access from
|
|
|
|
// JS may not be up to date.
|
2016-03-24 13:18:39 +00:00
|
|
|
props[key] = value.__getValue();
|
|
|
|
}
|
2016-09-19 11:07:36 +00:00
|
|
|
} else if (value instanceof AnimatedEvent) {
|
|
|
|
props[key] = value.__getHandler();
|
2015-07-07 20:34:09 +00:00
|
|
|
} else {
|
|
|
|
props[key] = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return props;
|
|
|
|
}
|
|
|
|
|
2015-08-20 03:06:22 +00:00
|
|
|
__getAnimatedValue(): Object {
|
2015-07-07 20:34:09 +00:00
|
|
|
var props = {};
|
|
|
|
for (var key in this._props) {
|
|
|
|
var value = this._props[key];
|
|
|
|
if (value instanceof Animated) {
|
2015-08-20 03:06:22 +00:00
|
|
|
props[key] = value.__getAnimatedValue();
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return props;
|
|
|
|
}
|
|
|
|
|
2015-08-20 03:06:22 +00:00
|
|
|
__attach(): void {
|
2015-07-07 20:34:09 +00:00
|
|
|
for (var key in this._props) {
|
|
|
|
var value = this._props[key];
|
|
|
|
if (value instanceof Animated) {
|
2015-08-20 03:06:22 +00:00
|
|
|
value.__addChild(this);
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-20 03:06:22 +00:00
|
|
|
__detach(): void {
|
2016-03-24 13:18:39 +00:00
|
|
|
if (this.__isNative && this._animatedView) {
|
|
|
|
this.__disconnectAnimatedView();
|
|
|
|
}
|
2015-07-07 20:34:09 +00:00
|
|
|
for (var key in this._props) {
|
|
|
|
var value = this._props[key];
|
|
|
|
if (value instanceof Animated) {
|
2015-08-20 03:06:22 +00:00
|
|
|
value.__removeChild(this);
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
}
|
2016-03-24 13:18:39 +00:00
|
|
|
super.__detach();
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
update(): void {
|
|
|
|
this._callback();
|
|
|
|
}
|
2016-03-24 13:18:39 +00:00
|
|
|
|
|
|
|
__makeNative(): void {
|
|
|
|
if (!this.__isNative) {
|
|
|
|
this.__isNative = true;
|
|
|
|
for (var key in this._props) {
|
|
|
|
var value = this._props[key];
|
|
|
|
if (value instanceof Animated) {
|
|
|
|
value.__makeNative();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (this._animatedView) {
|
|
|
|
this.__connectAnimatedView();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
setNativeView(animatedView: any): void {
|
2017-03-18 05:02:04 +00:00
|
|
|
if (this._animatedView === animatedView) {
|
|
|
|
return;
|
|
|
|
}
|
2016-03-24 13:18:39 +00:00
|
|
|
this._animatedView = animatedView;
|
|
|
|
if (this.__isNative) {
|
|
|
|
this.__connectAnimatedView();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
__connectAnimatedView(): void {
|
|
|
|
invariant(this.__isNative, 'Expected node to be marked as "native"');
|
2017-02-09 03:14:03 +00:00
|
|
|
var nativeViewTag: ?number = ReactNative.findNodeHandle(this._animatedView);
|
2016-03-24 13:18:39 +00:00
|
|
|
invariant(nativeViewTag != null, 'Unable to locate attached view in the native tree');
|
|
|
|
NativeAnimatedAPI.connectAnimatedNodeToView(this.__getNativeTag(), nativeViewTag);
|
|
|
|
}
|
|
|
|
|
|
|
|
__disconnectAnimatedView(): void {
|
|
|
|
invariant(this.__isNative, 'Expected node to be marked as "native"');
|
2017-02-09 03:14:03 +00:00
|
|
|
var nativeViewTag: ?number = ReactNative.findNodeHandle(this._animatedView);
|
2016-03-24 13:18:39 +00:00
|
|
|
invariant(nativeViewTag != null, 'Unable to locate attached view in the native tree');
|
|
|
|
NativeAnimatedAPI.disconnectAnimatedNodeFromView(this.__getNativeTag(), nativeViewTag);
|
|
|
|
}
|
|
|
|
|
|
|
|
__getNativeConfig(): Object {
|
|
|
|
var propsConfig = {};
|
2016-08-09 13:32:41 +00:00
|
|
|
for (const propKey in this._props) {
|
2016-03-24 13:18:39 +00:00
|
|
|
var value = this._props[propKey];
|
|
|
|
if (value instanceof Animated) {
|
|
|
|
propsConfig[propKey] = value.__getNativeTag();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
type: 'props',
|
|
|
|
props: propsConfig,
|
|
|
|
};
|
|
|
|
}
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function createAnimatedComponent(Component: any): any {
|
|
|
|
class AnimatedComponent extends React.Component {
|
2016-08-11 11:18:45 +00:00
|
|
|
_component: any;
|
2017-03-18 05:02:04 +00:00
|
|
|
_prevComponent: any;
|
2015-07-07 20:34:09 +00:00
|
|
|
_propsAnimated: AnimatedProps;
|
2017-03-18 05:02:04 +00:00
|
|
|
_eventDetachers: Array<Function> = [];
|
2016-08-11 19:10:22 +00:00
|
|
|
_setComponentRef: Function;
|
|
|
|
|
|
|
|
constructor(props: Object) {
|
|
|
|
super(props);
|
|
|
|
this._setComponentRef = this._setComponentRef.bind(this);
|
|
|
|
}
|
2015-07-07 20:34:09 +00:00
|
|
|
|
|
|
|
componentWillUnmount() {
|
2015-08-20 03:06:22 +00:00
|
|
|
this._propsAnimated && this._propsAnimated.__detach();
|
2017-03-18 05:02:04 +00:00
|
|
|
this._detachNativeEvents();
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
setNativeProps(props) {
|
2016-08-11 11:18:45 +00:00
|
|
|
this._component.setNativeProps(props);
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
componentWillMount() {
|
2016-09-19 11:07:36 +00:00
|
|
|
this._attachProps(this.props);
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
|
2016-03-24 13:18:39 +00:00
|
|
|
componentDidMount() {
|
2016-08-11 11:18:45 +00:00
|
|
|
this._propsAnimated.setNativeView(this._component);
|
2017-03-18 05:02:04 +00:00
|
|
|
this._attachNativeEvents();
|
2016-09-19 11:07:36 +00:00
|
|
|
}
|
|
|
|
|
2017-03-18 05:02:04 +00:00
|
|
|
_attachNativeEvents() {
|
2016-09-19 11:07:36 +00:00
|
|
|
// Make sure to get the scrollable node for components that implement
|
|
|
|
// `ScrollResponder.Mixin`.
|
2017-03-18 05:02:04 +00:00
|
|
|
const scrollableNode = this._component.getScrollableNode ?
|
2016-09-19 11:07:36 +00:00
|
|
|
this._component.getScrollableNode() :
|
|
|
|
this._component;
|
|
|
|
|
2017-03-18 05:02:04 +00:00
|
|
|
for (const key in this.props) {
|
|
|
|
const prop = this.props[key];
|
2016-09-19 11:07:36 +00:00
|
|
|
if (prop instanceof AnimatedEvent && prop.__isNative) {
|
2017-03-18 05:02:04 +00:00
|
|
|
prop.__attach(scrollableNode, key);
|
|
|
|
this._eventDetachers.push(() => prop.__detach(scrollableNode, key));
|
2016-09-19 11:07:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-18 05:02:04 +00:00
|
|
|
_detachNativeEvents() {
|
|
|
|
this._eventDetachers.forEach(remove => remove());
|
|
|
|
this._eventDetachers = [];
|
2016-03-24 13:18:39 +00:00
|
|
|
}
|
|
|
|
|
2016-09-19 11:07:36 +00:00
|
|
|
_attachProps(nextProps) {
|
2015-07-07 20:34:09 +00:00
|
|
|
var oldPropsAnimated = this._propsAnimated;
|
|
|
|
|
|
|
|
// The system is best designed when setNativeProps is implemented. It is
|
|
|
|
// able to avoid re-rendering and directly set the attributes that
|
|
|
|
// changed. However, setNativeProps can only be implemented on leaf
|
|
|
|
// native components. If you want to animate a composite component, you
|
|
|
|
// need to re-render it. In this case, we have a fallback that uses
|
|
|
|
// forceUpdate.
|
|
|
|
var callback = () => {
|
2016-08-11 11:18:45 +00:00
|
|
|
if (this._component.setNativeProps) {
|
2016-03-24 13:18:39 +00:00
|
|
|
if (!this._propsAnimated.__isNative) {
|
2016-08-11 11:18:45 +00:00
|
|
|
this._component.setNativeProps(
|
2016-03-24 13:18:39 +00:00
|
|
|
this._propsAnimated.__getAnimatedValue()
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
throw new Error('Attempting to run JS driven animation on animated '
|
|
|
|
+ 'node that has been moved to "native" earlier by starting an '
|
|
|
|
+ 'animation with `useNativeDriver: true`');
|
|
|
|
}
|
2015-07-07 20:34:09 +00:00
|
|
|
} else {
|
|
|
|
this.forceUpdate();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
this._propsAnimated = new AnimatedProps(
|
|
|
|
nextProps,
|
|
|
|
callback,
|
|
|
|
);
|
|
|
|
|
|
|
|
// When you call detach, it removes the element from the parent list
|
|
|
|
// of children. If it goes to 0, then the parent also detaches itself
|
|
|
|
// and so on.
|
|
|
|
// An optimization is to attach the new elements and THEN detach the old
|
|
|
|
// ones instead of detaching and THEN attaching.
|
|
|
|
// This way the intermediate state isn't to go to 0 and trigger
|
|
|
|
// this expensive recursive detaching to then re-attach everything on
|
|
|
|
// the very next operation.
|
2015-08-20 03:06:22 +00:00
|
|
|
oldPropsAnimated && oldPropsAnimated.__detach();
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
|
2017-03-18 05:02:04 +00:00
|
|
|
componentWillReceiveProps(newProps) {
|
|
|
|
this._attachProps(newProps);
|
|
|
|
}
|
|
|
|
|
|
|
|
componentDidUpdate(prevProps) {
|
|
|
|
if (this._component !== this._prevComponent) {
|
|
|
|
this._propsAnimated.setNativeView(this._component);
|
|
|
|
}
|
|
|
|
if (this._component !== this._prevComponent || prevProps !== this.props) {
|
|
|
|
this._detachNativeEvents();
|
|
|
|
this._attachNativeEvents();
|
|
|
|
}
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
|
|
|
return (
|
|
|
|
<Component
|
|
|
|
{...this._propsAnimated.__getValue()}
|
2016-08-11 11:18:45 +00:00
|
|
|
ref={this._setComponentRef}
|
2015-07-07 20:34:09 +00:00
|
|
|
/>
|
|
|
|
);
|
|
|
|
}
|
2016-08-11 11:18:45 +00:00
|
|
|
|
2016-08-11 19:10:22 +00:00
|
|
|
_setComponentRef(c) {
|
2017-03-18 05:02:04 +00:00
|
|
|
this._prevComponent = this._component;
|
2016-08-11 11:18:45 +00:00
|
|
|
this._component = c;
|
|
|
|
}
|
2016-09-19 10:51:48 +00:00
|
|
|
|
|
|
|
// A third party library can use getNode()
|
|
|
|
// to get the node reference of the decorated component
|
|
|
|
getNode () {
|
|
|
|
return this._component;
|
|
|
|
}
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
2017-03-28 18:17:21 +00:00
|
|
|
|
|
|
|
// ReactNative `View.propTypes` have been deprecated in favor of
|
|
|
|
// `ViewPropTypes`. In their place a temporary getter has been added with a
|
|
|
|
// deprecated warning message. Avoid triggering that warning here by using
|
|
|
|
// temporary workaround, __propTypesSecretDontUseThesePlease.
|
|
|
|
// TODO (bvaughn) Revert this particular change any time after April 1
|
|
|
|
var propTypes =
|
|
|
|
Component.__propTypesSecretDontUseThesePlease ||
|
|
|
|
Component.propTypes;
|
|
|
|
|
2015-10-06 22:19:58 +00:00
|
|
|
AnimatedComponent.propTypes = {
|
|
|
|
style: function(props, propName, componentName) {
|
2017-03-28 18:17:21 +00:00
|
|
|
if (!propTypes) {
|
2015-12-02 19:23:22 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-10-06 22:19:58 +00:00
|
|
|
for (var key in ViewStylePropTypes) {
|
2017-03-28 18:17:21 +00:00
|
|
|
if (!propTypes[key] && props[key] !== undefined) {
|
2016-04-19 01:52:53 +00:00
|
|
|
console.warn(
|
2015-10-06 22:19:58 +00:00
|
|
|
'You are setting the style `{ ' + key + ': ... }` as a prop. You ' +
|
|
|
|
'should nest it in a style object. ' +
|
|
|
|
'E.g. `{ style: { ' + key + ': ... } }`'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2016-09-19 11:07:36 +00:00
|
|
|
},
|
2015-10-06 22:19:58 +00:00
|
|
|
};
|
2015-07-07 20:34:09 +00:00
|
|
|
|
|
|
|
return AnimatedComponent;
|
|
|
|
}
|
|
|
|
|
|
|
|
class AnimatedTracking extends Animated {
|
|
|
|
_value: AnimatedValue;
|
|
|
|
_parent: Animated;
|
|
|
|
_callback: ?EndCallback;
|
|
|
|
_animationConfig: Object;
|
|
|
|
_animationClass: any;
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
value: AnimatedValue,
|
|
|
|
parent: Animated,
|
|
|
|
animationClass: any,
|
|
|
|
animationConfig: Object,
|
|
|
|
callback?: ?EndCallback,
|
|
|
|
) {
|
|
|
|
super();
|
|
|
|
this._value = value;
|
|
|
|
this._parent = parent;
|
|
|
|
this._animationClass = animationClass;
|
|
|
|
this._animationConfig = animationConfig;
|
|
|
|
this._callback = callback;
|
2015-08-20 03:06:22 +00:00
|
|
|
this.__attach();
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
__getValue(): Object {
|
|
|
|
return this._parent.__getValue();
|
|
|
|
}
|
|
|
|
|
2015-08-20 03:06:22 +00:00
|
|
|
__attach(): void {
|
|
|
|
this._parent.__addChild(this);
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
|
2015-08-20 03:06:22 +00:00
|
|
|
__detach(): void {
|
|
|
|
this._parent.__removeChild(this);
|
2016-03-24 13:18:39 +00:00
|
|
|
super.__detach();
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
update(): void {
|
|
|
|
this._value.animate(new this._animationClass({
|
|
|
|
...this._animationConfig,
|
|
|
|
toValue: (this._animationConfig.toValue: any).__getValue(),
|
|
|
|
}), this._callback);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type CompositeAnimation = {
|
2016-08-09 11:20:33 +00:00
|
|
|
start: (callback?: ?EndCallback) => void,
|
|
|
|
stop: () => void,
|
Adds Animated.loop to Animated API
Summary:
* Any animation can be looped on the javascript thread
* Only basic animations supported natively at this stage, loops run
using the native driver cannot contain animations of type sequence,
parallel, stagger, or loop
Motivation: We need a spinner in our app that is displayed and animated while the javascript thread is tied up with other tasks. This means it needs to be offloaded from the javascript thread, so that it will continue to run while those tasks are churning away.
I originally submitted PR #9513, which has served our needs, but brentvatne pointed out a better way to do it. Had hoped his suggestion would be implemented by janicduplessis or another fb employee, but after 5 months I thought I'd give it another push.
I've put together an implementation that basically matches the suggested API. Let me know what you think, and whether others can pick it up from here and get it in to core.
Personal Motivation: I am leaving my current organisation on Feb 10th, so am trying to clean thing
Closes https://github.com/facebook/react-native/pull/11973
Differential Revision: D4704381
fbshipit-source-id: 42a2cdf5d53a7c0d08f86a58485f7f38739e6cd9
2017-03-14 06:49:47 +00:00
|
|
|
reset: () => void,
|
|
|
|
_startNativeLoop: (iterations?: number) => void,
|
|
|
|
_isUsingNativeDriver: () => boolean,
|
2015-07-07 20:34:09 +00:00
|
|
|
};
|
|
|
|
|
2015-12-10 21:27:52 +00:00
|
|
|
var add = function(
|
2016-09-26 23:32:27 +00:00
|
|
|
a: Animated | number,
|
|
|
|
b: Animated | number,
|
2015-12-10 21:27:52 +00:00
|
|
|
): AnimatedAddition {
|
|
|
|
return new AnimatedAddition(a, b);
|
|
|
|
};
|
|
|
|
|
2016-09-26 23:32:27 +00:00
|
|
|
var divide = function(
|
|
|
|
a: Animated | number,
|
|
|
|
b: Animated | number,
|
|
|
|
): AnimatedDivision {
|
|
|
|
return new AnimatedDivision(a, b);
|
|
|
|
};
|
|
|
|
|
2015-12-10 21:27:52 +00:00
|
|
|
var multiply = function(
|
2016-09-26 23:32:27 +00:00
|
|
|
a: Animated | number,
|
|
|
|
b: Animated | number,
|
2015-12-10 21:27:52 +00:00
|
|
|
): AnimatedMultiplication {
|
|
|
|
return new AnimatedMultiplication(a, b);
|
|
|
|
};
|
|
|
|
|
2016-02-26 01:18:27 +00:00
|
|
|
var modulo = function(
|
|
|
|
a: Animated,
|
|
|
|
modulus: number
|
|
|
|
): AnimatedModulo {
|
|
|
|
return new AnimatedModulo(a, modulus);
|
|
|
|
};
|
|
|
|
|
2016-08-23 20:59:07 +00:00
|
|
|
var diffClamp = function(
|
|
|
|
a: Animated,
|
|
|
|
min: number,
|
|
|
|
max: number,
|
|
|
|
): AnimatedDiffClamp {
|
|
|
|
return new AnimatedDiffClamp(a, min, max);
|
|
|
|
};
|
|
|
|
|
2016-08-18 11:47:47 +00:00
|
|
|
const _combineCallbacks = function(callback: ?EndCallback, config : AnimationConfig) {
|
|
|
|
if (callback && config.onComplete) {
|
|
|
|
return (...args) => {
|
|
|
|
config.onComplete && config.onComplete(...args);
|
|
|
|
callback && callback(...args);
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
return callback || config.onComplete;
|
|
|
|
}
|
|
|
|
};
|
2016-02-26 01:18:27 +00:00
|
|
|
|
2015-07-07 20:34:09 +00:00
|
|
|
var maybeVectorAnim = function(
|
|
|
|
value: AnimatedValue | AnimatedValueXY,
|
|
|
|
config: Object,
|
|
|
|
anim: (value: AnimatedValue, config: Object) => CompositeAnimation
|
|
|
|
): ?CompositeAnimation {
|
|
|
|
if (value instanceof AnimatedValueXY) {
|
|
|
|
var configX = {...config};
|
|
|
|
var configY = {...config};
|
|
|
|
for (var key in config) {
|
|
|
|
var {x, y} = config[key];
|
|
|
|
if (x !== undefined && y !== undefined) {
|
|
|
|
configX[key] = x;
|
|
|
|
configY[key] = y;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var aX = anim((value: AnimatedValueXY).x, configX);
|
|
|
|
var aY = anim((value: AnimatedValueXY).y, configY);
|
|
|
|
// We use `stopTogether: false` here because otherwise tracking will break
|
|
|
|
// because the second animation will get stopped before it can update.
|
|
|
|
return parallel([aX, aY], {stopTogether: false});
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
};
|
|
|
|
|
|
|
|
var spring = function(
|
|
|
|
value: AnimatedValue | AnimatedValueXY,
|
|
|
|
config: SpringAnimationConfig,
|
|
|
|
): CompositeAnimation {
|
Adds Animated.loop to Animated API
Summary:
* Any animation can be looped on the javascript thread
* Only basic animations supported natively at this stage, loops run
using the native driver cannot contain animations of type sequence,
parallel, stagger, or loop
Motivation: We need a spinner in our app that is displayed and animated while the javascript thread is tied up with other tasks. This means it needs to be offloaded from the javascript thread, so that it will continue to run while those tasks are churning away.
I originally submitted PR #9513, which has served our needs, but brentvatne pointed out a better way to do it. Had hoped his suggestion would be implemented by janicduplessis or another fb employee, but after 5 months I thought I'd give it another push.
I've put together an implementation that basically matches the suggested API. Let me know what you think, and whether others can pick it up from here and get it in to core.
Personal Motivation: I am leaving my current organisation on Feb 10th, so am trying to clean thing
Closes https://github.com/facebook/react-native/pull/11973
Differential Revision: D4704381
fbshipit-source-id: 42a2cdf5d53a7c0d08f86a58485f7f38739e6cd9
2017-03-14 06:49:47 +00:00
|
|
|
var start = function(
|
|
|
|
animatedValue: AnimatedValue | AnimatedValueXY,
|
|
|
|
configuration: SpringAnimationConfig,
|
|
|
|
callback?: ?EndCallback): void {
|
|
|
|
callback = _combineCallbacks(callback, configuration);
|
|
|
|
var singleValue: any = animatedValue;
|
|
|
|
var singleConfig: any = configuration;
|
2015-07-07 20:34:09 +00:00
|
|
|
singleValue.stopTracking();
|
Adds Animated.loop to Animated API
Summary:
* Any animation can be looped on the javascript thread
* Only basic animations supported natively at this stage, loops run
using the native driver cannot contain animations of type sequence,
parallel, stagger, or loop
Motivation: We need a spinner in our app that is displayed and animated while the javascript thread is tied up with other tasks. This means it needs to be offloaded from the javascript thread, so that it will continue to run while those tasks are churning away.
I originally submitted PR #9513, which has served our needs, but brentvatne pointed out a better way to do it. Had hoped his suggestion would be implemented by janicduplessis or another fb employee, but after 5 months I thought I'd give it another push.
I've put together an implementation that basically matches the suggested API. Let me know what you think, and whether others can pick it up from here and get it in to core.
Personal Motivation: I am leaving my current organisation on Feb 10th, so am trying to clean thing
Closes https://github.com/facebook/react-native/pull/11973
Differential Revision: D4704381
fbshipit-source-id: 42a2cdf5d53a7c0d08f86a58485f7f38739e6cd9
2017-03-14 06:49:47 +00:00
|
|
|
if (configuration.toValue instanceof Animated) {
|
2015-07-07 20:34:09 +00:00
|
|
|
singleValue.track(new AnimatedTracking(
|
|
|
|
singleValue,
|
Adds Animated.loop to Animated API
Summary:
* Any animation can be looped on the javascript thread
* Only basic animations supported natively at this stage, loops run
using the native driver cannot contain animations of type sequence,
parallel, stagger, or loop
Motivation: We need a spinner in our app that is displayed and animated while the javascript thread is tied up with other tasks. This means it needs to be offloaded from the javascript thread, so that it will continue to run while those tasks are churning away.
I originally submitted PR #9513, which has served our needs, but brentvatne pointed out a better way to do it. Had hoped his suggestion would be implemented by janicduplessis or another fb employee, but after 5 months I thought I'd give it another push.
I've put together an implementation that basically matches the suggested API. Let me know what you think, and whether others can pick it up from here and get it in to core.
Personal Motivation: I am leaving my current organisation on Feb 10th, so am trying to clean thing
Closes https://github.com/facebook/react-native/pull/11973
Differential Revision: D4704381
fbshipit-source-id: 42a2cdf5d53a7c0d08f86a58485f7f38739e6cd9
2017-03-14 06:49:47 +00:00
|
|
|
configuration.toValue,
|
2015-07-07 20:34:09 +00:00
|
|
|
SpringAnimation,
|
|
|
|
singleConfig,
|
|
|
|
callback
|
|
|
|
));
|
|
|
|
} else {
|
|
|
|
singleValue.animate(new SpringAnimation(singleConfig), callback);
|
|
|
|
}
|
Adds Animated.loop to Animated API
Summary:
* Any animation can be looped on the javascript thread
* Only basic animations supported natively at this stage, loops run
using the native driver cannot contain animations of type sequence,
parallel, stagger, or loop
Motivation: We need a spinner in our app that is displayed and animated while the javascript thread is tied up with other tasks. This means it needs to be offloaded from the javascript thread, so that it will continue to run while those tasks are churning away.
I originally submitted PR #9513, which has served our needs, but brentvatne pointed out a better way to do it. Had hoped his suggestion would be implemented by janicduplessis or another fb employee, but after 5 months I thought I'd give it another push.
I've put together an implementation that basically matches the suggested API. Let me know what you think, and whether others can pick it up from here and get it in to core.
Personal Motivation: I am leaving my current organisation on Feb 10th, so am trying to clean thing
Closes https://github.com/facebook/react-native/pull/11973
Differential Revision: D4704381
fbshipit-source-id: 42a2cdf5d53a7c0d08f86a58485f7f38739e6cd9
2017-03-14 06:49:47 +00:00
|
|
|
};
|
|
|
|
return maybeVectorAnim(value, config, spring) || {
|
|
|
|
start: function(callback?: ?EndCallback): void {
|
|
|
|
start(value, config, callback);
|
2015-07-07 20:34:09 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
stop: function(): void {
|
|
|
|
value.stopAnimation();
|
|
|
|
},
|
Adds Animated.loop to Animated API
Summary:
* Any animation can be looped on the javascript thread
* Only basic animations supported natively at this stage, loops run
using the native driver cannot contain animations of type sequence,
parallel, stagger, or loop
Motivation: We need a spinner in our app that is displayed and animated while the javascript thread is tied up with other tasks. This means it needs to be offloaded from the javascript thread, so that it will continue to run while those tasks are churning away.
I originally submitted PR #9513, which has served our needs, but brentvatne pointed out a better way to do it. Had hoped his suggestion would be implemented by janicduplessis or another fb employee, but after 5 months I thought I'd give it another push.
I've put together an implementation that basically matches the suggested API. Let me know what you think, and whether others can pick it up from here and get it in to core.
Personal Motivation: I am leaving my current organisation on Feb 10th, so am trying to clean thing
Closes https://github.com/facebook/react-native/pull/11973
Differential Revision: D4704381
fbshipit-source-id: 42a2cdf5d53a7c0d08f86a58485f7f38739e6cd9
2017-03-14 06:49:47 +00:00
|
|
|
|
|
|
|
reset: function(): void {
|
|
|
|
value.resetAnimation();
|
|
|
|
},
|
|
|
|
|
|
|
|
_startNativeLoop: function(iterations?: number): void {
|
|
|
|
var singleConfig = { ...config, iterations };
|
|
|
|
start(value, singleConfig);
|
|
|
|
},
|
|
|
|
|
|
|
|
_isUsingNativeDriver: function(): boolean {
|
|
|
|
return config.useNativeDriver || false;
|
|
|
|
}
|
2015-07-07 20:34:09 +00:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
var timing = function(
|
|
|
|
value: AnimatedValue | AnimatedValueXY,
|
|
|
|
config: TimingAnimationConfig,
|
|
|
|
): CompositeAnimation {
|
Adds Animated.loop to Animated API
Summary:
* Any animation can be looped on the javascript thread
* Only basic animations supported natively at this stage, loops run
using the native driver cannot contain animations of type sequence,
parallel, stagger, or loop
Motivation: We need a spinner in our app that is displayed and animated while the javascript thread is tied up with other tasks. This means it needs to be offloaded from the javascript thread, so that it will continue to run while those tasks are churning away.
I originally submitted PR #9513, which has served our needs, but brentvatne pointed out a better way to do it. Had hoped his suggestion would be implemented by janicduplessis or another fb employee, but after 5 months I thought I'd give it another push.
I've put together an implementation that basically matches the suggested API. Let me know what you think, and whether others can pick it up from here and get it in to core.
Personal Motivation: I am leaving my current organisation on Feb 10th, so am trying to clean thing
Closes https://github.com/facebook/react-native/pull/11973
Differential Revision: D4704381
fbshipit-source-id: 42a2cdf5d53a7c0d08f86a58485f7f38739e6cd9
2017-03-14 06:49:47 +00:00
|
|
|
var start = function(
|
|
|
|
animatedValue: AnimatedValue | AnimatedValueXY,
|
|
|
|
configuration: TimingAnimationConfig,
|
|
|
|
callback?: ?EndCallback): void {
|
|
|
|
callback = _combineCallbacks(callback, configuration);
|
|
|
|
var singleValue: any = animatedValue;
|
|
|
|
var singleConfig: any = configuration;
|
2015-07-07 20:34:09 +00:00
|
|
|
singleValue.stopTracking();
|
Adds Animated.loop to Animated API
Summary:
* Any animation can be looped on the javascript thread
* Only basic animations supported natively at this stage, loops run
using the native driver cannot contain animations of type sequence,
parallel, stagger, or loop
Motivation: We need a spinner in our app that is displayed and animated while the javascript thread is tied up with other tasks. This means it needs to be offloaded from the javascript thread, so that it will continue to run while those tasks are churning away.
I originally submitted PR #9513, which has served our needs, but brentvatne pointed out a better way to do it. Had hoped his suggestion would be implemented by janicduplessis or another fb employee, but after 5 months I thought I'd give it another push.
I've put together an implementation that basically matches the suggested API. Let me know what you think, and whether others can pick it up from here and get it in to core.
Personal Motivation: I am leaving my current organisation on Feb 10th, so am trying to clean thing
Closes https://github.com/facebook/react-native/pull/11973
Differential Revision: D4704381
fbshipit-source-id: 42a2cdf5d53a7c0d08f86a58485f7f38739e6cd9
2017-03-14 06:49:47 +00:00
|
|
|
if (configuration.toValue instanceof Animated) {
|
2015-07-07 20:34:09 +00:00
|
|
|
singleValue.track(new AnimatedTracking(
|
|
|
|
singleValue,
|
Adds Animated.loop to Animated API
Summary:
* Any animation can be looped on the javascript thread
* Only basic animations supported natively at this stage, loops run
using the native driver cannot contain animations of type sequence,
parallel, stagger, or loop
Motivation: We need a spinner in our app that is displayed and animated while the javascript thread is tied up with other tasks. This means it needs to be offloaded from the javascript thread, so that it will continue to run while those tasks are churning away.
I originally submitted PR #9513, which has served our needs, but brentvatne pointed out a better way to do it. Had hoped his suggestion would be implemented by janicduplessis or another fb employee, but after 5 months I thought I'd give it another push.
I've put together an implementation that basically matches the suggested API. Let me know what you think, and whether others can pick it up from here and get it in to core.
Personal Motivation: I am leaving my current organisation on Feb 10th, so am trying to clean thing
Closes https://github.com/facebook/react-native/pull/11973
Differential Revision: D4704381
fbshipit-source-id: 42a2cdf5d53a7c0d08f86a58485f7f38739e6cd9
2017-03-14 06:49:47 +00:00
|
|
|
configuration.toValue,
|
2015-07-07 20:34:09 +00:00
|
|
|
TimingAnimation,
|
|
|
|
singleConfig,
|
|
|
|
callback
|
|
|
|
));
|
|
|
|
} else {
|
|
|
|
singleValue.animate(new TimingAnimation(singleConfig), callback);
|
|
|
|
}
|
Adds Animated.loop to Animated API
Summary:
* Any animation can be looped on the javascript thread
* Only basic animations supported natively at this stage, loops run
using the native driver cannot contain animations of type sequence,
parallel, stagger, or loop
Motivation: We need a spinner in our app that is displayed and animated while the javascript thread is tied up with other tasks. This means it needs to be offloaded from the javascript thread, so that it will continue to run while those tasks are churning away.
I originally submitted PR #9513, which has served our needs, but brentvatne pointed out a better way to do it. Had hoped his suggestion would be implemented by janicduplessis or another fb employee, but after 5 months I thought I'd give it another push.
I've put together an implementation that basically matches the suggested API. Let me know what you think, and whether others can pick it up from here and get it in to core.
Personal Motivation: I am leaving my current organisation on Feb 10th, so am trying to clean thing
Closes https://github.com/facebook/react-native/pull/11973
Differential Revision: D4704381
fbshipit-source-id: 42a2cdf5d53a7c0d08f86a58485f7f38739e6cd9
2017-03-14 06:49:47 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
return maybeVectorAnim(value, config, timing) || {
|
|
|
|
start: function(callback?: ?EndCallback): void {
|
|
|
|
start(value, config, callback);
|
2015-07-07 20:34:09 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
stop: function(): void {
|
|
|
|
value.stopAnimation();
|
|
|
|
},
|
Adds Animated.loop to Animated API
Summary:
* Any animation can be looped on the javascript thread
* Only basic animations supported natively at this stage, loops run
using the native driver cannot contain animations of type sequence,
parallel, stagger, or loop
Motivation: We need a spinner in our app that is displayed and animated while the javascript thread is tied up with other tasks. This means it needs to be offloaded from the javascript thread, so that it will continue to run while those tasks are churning away.
I originally submitted PR #9513, which has served our needs, but brentvatne pointed out a better way to do it. Had hoped his suggestion would be implemented by janicduplessis or another fb employee, but after 5 months I thought I'd give it another push.
I've put together an implementation that basically matches the suggested API. Let me know what you think, and whether others can pick it up from here and get it in to core.
Personal Motivation: I am leaving my current organisation on Feb 10th, so am trying to clean thing
Closes https://github.com/facebook/react-native/pull/11973
Differential Revision: D4704381
fbshipit-source-id: 42a2cdf5d53a7c0d08f86a58485f7f38739e6cd9
2017-03-14 06:49:47 +00:00
|
|
|
|
|
|
|
reset: function(): void {
|
|
|
|
value.resetAnimation();
|
|
|
|
},
|
|
|
|
|
|
|
|
_startNativeLoop: function(iterations?: number): void {
|
|
|
|
var singleConfig = { ...config, iterations };
|
|
|
|
start(value, singleConfig);
|
|
|
|
},
|
|
|
|
|
|
|
|
_isUsingNativeDriver: function(): boolean {
|
|
|
|
return config.useNativeDriver || false;
|
|
|
|
}
|
2015-07-07 20:34:09 +00:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
var decay = function(
|
|
|
|
value: AnimatedValue | AnimatedValueXY,
|
|
|
|
config: DecayAnimationConfig,
|
|
|
|
): CompositeAnimation {
|
Adds Animated.loop to Animated API
Summary:
* Any animation can be looped on the javascript thread
* Only basic animations supported natively at this stage, loops run
using the native driver cannot contain animations of type sequence,
parallel, stagger, or loop
Motivation: We need a spinner in our app that is displayed and animated while the javascript thread is tied up with other tasks. This means it needs to be offloaded from the javascript thread, so that it will continue to run while those tasks are churning away.
I originally submitted PR #9513, which has served our needs, but brentvatne pointed out a better way to do it. Had hoped his suggestion would be implemented by janicduplessis or another fb employee, but after 5 months I thought I'd give it another push.
I've put together an implementation that basically matches the suggested API. Let me know what you think, and whether others can pick it up from here and get it in to core.
Personal Motivation: I am leaving my current organisation on Feb 10th, so am trying to clean thing
Closes https://github.com/facebook/react-native/pull/11973
Differential Revision: D4704381
fbshipit-source-id: 42a2cdf5d53a7c0d08f86a58485f7f38739e6cd9
2017-03-14 06:49:47 +00:00
|
|
|
var start = function(
|
|
|
|
animatedValue: AnimatedValue | AnimatedValueXY,
|
|
|
|
configuration: DecayAnimationConfig,
|
|
|
|
callback?: ?EndCallback): void {
|
|
|
|
callback = _combineCallbacks(callback, configuration);
|
|
|
|
var singleValue: any = animatedValue;
|
|
|
|
var singleConfig: any = configuration;
|
2015-07-07 20:34:09 +00:00
|
|
|
singleValue.stopTracking();
|
|
|
|
singleValue.animate(new DecayAnimation(singleConfig), callback);
|
Adds Animated.loop to Animated API
Summary:
* Any animation can be looped on the javascript thread
* Only basic animations supported natively at this stage, loops run
using the native driver cannot contain animations of type sequence,
parallel, stagger, or loop
Motivation: We need a spinner in our app that is displayed and animated while the javascript thread is tied up with other tasks. This means it needs to be offloaded from the javascript thread, so that it will continue to run while those tasks are churning away.
I originally submitted PR #9513, which has served our needs, but brentvatne pointed out a better way to do it. Had hoped his suggestion would be implemented by janicduplessis or another fb employee, but after 5 months I thought I'd give it another push.
I've put together an implementation that basically matches the suggested API. Let me know what you think, and whether others can pick it up from here and get it in to core.
Personal Motivation: I am leaving my current organisation on Feb 10th, so am trying to clean thing
Closes https://github.com/facebook/react-native/pull/11973
Differential Revision: D4704381
fbshipit-source-id: 42a2cdf5d53a7c0d08f86a58485f7f38739e6cd9
2017-03-14 06:49:47 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
return maybeVectorAnim(value, config, decay) || {
|
|
|
|
start: function(callback?: ?EndCallback): void {
|
|
|
|
start(value, config, callback);
|
2015-07-07 20:34:09 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
stop: function(): void {
|
|
|
|
value.stopAnimation();
|
|
|
|
},
|
Adds Animated.loop to Animated API
Summary:
* Any animation can be looped on the javascript thread
* Only basic animations supported natively at this stage, loops run
using the native driver cannot contain animations of type sequence,
parallel, stagger, or loop
Motivation: We need a spinner in our app that is displayed and animated while the javascript thread is tied up with other tasks. This means it needs to be offloaded from the javascript thread, so that it will continue to run while those tasks are churning away.
I originally submitted PR #9513, which has served our needs, but brentvatne pointed out a better way to do it. Had hoped his suggestion would be implemented by janicduplessis or another fb employee, but after 5 months I thought I'd give it another push.
I've put together an implementation that basically matches the suggested API. Let me know what you think, and whether others can pick it up from here and get it in to core.
Personal Motivation: I am leaving my current organisation on Feb 10th, so am trying to clean thing
Closes https://github.com/facebook/react-native/pull/11973
Differential Revision: D4704381
fbshipit-source-id: 42a2cdf5d53a7c0d08f86a58485f7f38739e6cd9
2017-03-14 06:49:47 +00:00
|
|
|
|
|
|
|
reset: function(): void {
|
|
|
|
value.resetAnimation();
|
|
|
|
},
|
|
|
|
|
|
|
|
_startNativeLoop: function(iterations?: number): void {
|
|
|
|
var singleConfig = { ...config, iterations };
|
|
|
|
start(value, singleConfig);
|
|
|
|
},
|
|
|
|
|
|
|
|
_isUsingNativeDriver: function(): boolean {
|
|
|
|
return config.useNativeDriver || false;
|
|
|
|
}
|
2015-07-07 20:34:09 +00:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
var sequence = function(
|
|
|
|
animations: Array<CompositeAnimation>,
|
|
|
|
): CompositeAnimation {
|
|
|
|
var current = 0;
|
|
|
|
return {
|
|
|
|
start: function(callback?: ?EndCallback) {
|
|
|
|
var onComplete = function(result) {
|
|
|
|
if (!result.finished) {
|
|
|
|
callback && callback(result);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
current++;
|
|
|
|
|
|
|
|
if (current === animations.length) {
|
|
|
|
callback && callback(result);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
animations[current].start(onComplete);
|
|
|
|
};
|
|
|
|
|
|
|
|
if (animations.length === 0) {
|
|
|
|
callback && callback({finished: true});
|
|
|
|
} else {
|
|
|
|
animations[current].start(onComplete);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
stop: function() {
|
|
|
|
if (current < animations.length) {
|
|
|
|
animations[current].stop();
|
|
|
|
}
|
Adds Animated.loop to Animated API
Summary:
* Any animation can be looped on the javascript thread
* Only basic animations supported natively at this stage, loops run
using the native driver cannot contain animations of type sequence,
parallel, stagger, or loop
Motivation: We need a spinner in our app that is displayed and animated while the javascript thread is tied up with other tasks. This means it needs to be offloaded from the javascript thread, so that it will continue to run while those tasks are churning away.
I originally submitted PR #9513, which has served our needs, but brentvatne pointed out a better way to do it. Had hoped his suggestion would be implemented by janicduplessis or another fb employee, but after 5 months I thought I'd give it another push.
I've put together an implementation that basically matches the suggested API. Let me know what you think, and whether others can pick it up from here and get it in to core.
Personal Motivation: I am leaving my current organisation on Feb 10th, so am trying to clean thing
Closes https://github.com/facebook/react-native/pull/11973
Differential Revision: D4704381
fbshipit-source-id: 42a2cdf5d53a7c0d08f86a58485f7f38739e6cd9
2017-03-14 06:49:47 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
reset: function() {
|
|
|
|
animations.forEach((animation, idx) => {
|
|
|
|
if (idx <= current) {
|
|
|
|
animation.reset();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
current = 0;
|
|
|
|
},
|
|
|
|
|
|
|
|
_startNativeLoop: function() {
|
|
|
|
throw new Error('Loops run using the native driver cannot contain Animated.sequence animations');
|
|
|
|
},
|
|
|
|
|
|
|
|
_isUsingNativeDriver: function(): boolean {
|
|
|
|
return false;
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
type ParallelConfig = {
|
2016-08-09 11:20:33 +00:00
|
|
|
stopTogether?: bool, // If one is stopped, stop all. default: true
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
var parallel = function(
|
|
|
|
animations: Array<CompositeAnimation>,
|
|
|
|
config?: ?ParallelConfig,
|
|
|
|
): CompositeAnimation {
|
|
|
|
var doneCount = 0;
|
|
|
|
// Make sure we only call stop() at most once for each animation
|
|
|
|
var hasEnded = {};
|
|
|
|
var stopTogether = !(config && config.stopTogether === false);
|
|
|
|
|
|
|
|
var result = {
|
|
|
|
start: function(callback?: ?EndCallback) {
|
|
|
|
if (doneCount === animations.length) {
|
|
|
|
callback && callback({finished: true});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
animations.forEach((animation, idx) => {
|
2015-07-21 22:04:02 +00:00
|
|
|
var cb = function(endResult) {
|
2015-07-07 20:34:09 +00:00
|
|
|
hasEnded[idx] = true;
|
|
|
|
doneCount++;
|
|
|
|
if (doneCount === animations.length) {
|
|
|
|
doneCount = 0;
|
|
|
|
callback && callback(endResult);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!endResult.finished && stopTogether) {
|
|
|
|
result.stop();
|
|
|
|
}
|
2015-07-21 22:04:02 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
if (!animation) {
|
|
|
|
cb({finished: true});
|
|
|
|
} else {
|
|
|
|
animation.start(cb);
|
|
|
|
}
|
2015-07-07 20:34:09 +00:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
stop: function(): void {
|
|
|
|
animations.forEach((animation, idx) => {
|
|
|
|
!hasEnded[idx] && animation.stop();
|
|
|
|
hasEnded[idx] = true;
|
|
|
|
});
|
Adds Animated.loop to Animated API
Summary:
* Any animation can be looped on the javascript thread
* Only basic animations supported natively at this stage, loops run
using the native driver cannot contain animations of type sequence,
parallel, stagger, or loop
Motivation: We need a spinner in our app that is displayed and animated while the javascript thread is tied up with other tasks. This means it needs to be offloaded from the javascript thread, so that it will continue to run while those tasks are churning away.
I originally submitted PR #9513, which has served our needs, but brentvatne pointed out a better way to do it. Had hoped his suggestion would be implemented by janicduplessis or another fb employee, but after 5 months I thought I'd give it another push.
I've put together an implementation that basically matches the suggested API. Let me know what you think, and whether others can pick it up from here and get it in to core.
Personal Motivation: I am leaving my current organisation on Feb 10th, so am trying to clean thing
Closes https://github.com/facebook/react-native/pull/11973
Differential Revision: D4704381
fbshipit-source-id: 42a2cdf5d53a7c0d08f86a58485f7f38739e6cd9
2017-03-14 06:49:47 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
reset: function(): void {
|
|
|
|
animations.forEach((animation, idx) => {
|
|
|
|
animation.reset();
|
|
|
|
hasEnded[idx] = false;
|
|
|
|
doneCount = 0;
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
_startNativeLoop: function() {
|
|
|
|
throw new Error('Loops run using the native driver cannot contain Animated.parallel animations');
|
|
|
|
},
|
|
|
|
|
|
|
|
_isUsingNativeDriver: function(): boolean {
|
|
|
|
return false;
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
|
|
|
var delay = function(time: number): CompositeAnimation {
|
|
|
|
// Would be nice to make a specialized implementation
|
|
|
|
return timing(new AnimatedValue(0), {toValue: 0, delay: time, duration: 0});
|
|
|
|
};
|
|
|
|
|
|
|
|
var stagger = function(
|
|
|
|
time: number,
|
|
|
|
animations: Array<CompositeAnimation>,
|
|
|
|
): CompositeAnimation {
|
|
|
|
return parallel(animations.map((animation, i) => {
|
|
|
|
return sequence([
|
|
|
|
delay(time * i),
|
|
|
|
animation,
|
|
|
|
]);
|
|
|
|
}));
|
|
|
|
};
|
|
|
|
|
Adds Animated.loop to Animated API
Summary:
* Any animation can be looped on the javascript thread
* Only basic animations supported natively at this stage, loops run
using the native driver cannot contain animations of type sequence,
parallel, stagger, or loop
Motivation: We need a spinner in our app that is displayed and animated while the javascript thread is tied up with other tasks. This means it needs to be offloaded from the javascript thread, so that it will continue to run while those tasks are churning away.
I originally submitted PR #9513, which has served our needs, but brentvatne pointed out a better way to do it. Had hoped his suggestion would be implemented by janicduplessis or another fb employee, but after 5 months I thought I'd give it another push.
I've put together an implementation that basically matches the suggested API. Let me know what you think, and whether others can pick it up from here and get it in to core.
Personal Motivation: I am leaving my current organisation on Feb 10th, so am trying to clean thing
Closes https://github.com/facebook/react-native/pull/11973
Differential Revision: D4704381
fbshipit-source-id: 42a2cdf5d53a7c0d08f86a58485f7f38739e6cd9
2017-03-14 06:49:47 +00:00
|
|
|
type LoopAnimationConfig = { iterations: number };
|
|
|
|
|
|
|
|
var loop = function(
|
|
|
|
animation: CompositeAnimation,
|
|
|
|
{ iterations = -1 }: LoopAnimationConfig = {},
|
|
|
|
): CompositeAnimation {
|
|
|
|
var isFinished = false;
|
|
|
|
var iterationsSoFar = 0;
|
|
|
|
return {
|
|
|
|
start: function(callback?: ?EndCallback) {
|
|
|
|
var restart = function(result: EndResult = {finished: true}): void {
|
|
|
|
if (isFinished ||
|
|
|
|
(iterationsSoFar === iterations) ||
|
|
|
|
(result.finished === false)) {
|
|
|
|
callback && callback(result);
|
|
|
|
} else {
|
|
|
|
iterationsSoFar++;
|
|
|
|
animation.reset();
|
|
|
|
animation.start(restart);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
if (!animation || iterations === 0) {
|
|
|
|
callback && callback({finished: true});
|
|
|
|
} else {
|
|
|
|
if (animation._isUsingNativeDriver()) {
|
|
|
|
animation._startNativeLoop(iterations);
|
|
|
|
} else {
|
|
|
|
restart(); // Start looping recursively on the js thread
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
stop: function(): void {
|
|
|
|
isFinished = true;
|
|
|
|
animation.stop();
|
|
|
|
},
|
|
|
|
|
|
|
|
reset: function(): void {
|
|
|
|
iterationsSoFar = 0;
|
|
|
|
isFinished = false;
|
|
|
|
animation.reset();
|
|
|
|
},
|
|
|
|
|
|
|
|
_startNativeLoop: function() {
|
|
|
|
throw new Error('Loops run using the native driver cannot contain Animated.loop animations');
|
|
|
|
},
|
|
|
|
|
|
|
|
_isUsingNativeDriver: function(): boolean {
|
|
|
|
return animation._isUsingNativeDriver();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2015-07-07 20:34:09 +00:00
|
|
|
type Mapping = {[key: string]: Mapping} | AnimatedValue;
|
2016-09-19 11:07:36 +00:00
|
|
|
type EventConfig = {
|
2016-11-02 06:55:21 +00:00
|
|
|
listener?: ?Function,
|
|
|
|
useNativeDriver?: bool,
|
2016-09-19 11:07:36 +00:00
|
|
|
};
|
2015-07-07 20:34:09 +00:00
|
|
|
|
2017-03-02 23:09:39 +00:00
|
|
|
function attachNativeEvent(viewRef: any, eventName: string, argMapping: Array<?Mapping>) {
|
|
|
|
// Find animated values in `argMapping` and create an array representing their
|
|
|
|
// key path inside the `nativeEvent` object. Ex.: ['contentOffset', 'x'].
|
|
|
|
const eventMappings = [];
|
|
|
|
|
|
|
|
const traverse = (value, path) => {
|
|
|
|
if (value instanceof AnimatedValue) {
|
|
|
|
value.__makeNative();
|
|
|
|
|
|
|
|
eventMappings.push({
|
|
|
|
nativeEventPath: path,
|
|
|
|
animatedValueTag: value.__getNativeTag(),
|
|
|
|
});
|
|
|
|
} else if (typeof value === 'object') {
|
|
|
|
for (const key in value) {
|
|
|
|
traverse(value[key], path.concat(key));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
invariant(
|
|
|
|
argMapping[0] && argMapping[0].nativeEvent,
|
|
|
|
'Native driven events only support animated values contained inside `nativeEvent`.'
|
|
|
|
);
|
|
|
|
|
|
|
|
// Assume that the event containing `nativeEvent` is always the first argument.
|
|
|
|
traverse(argMapping[0].nativeEvent, []);
|
|
|
|
|
|
|
|
const viewTag = ReactNative.findNodeHandle(viewRef);
|
|
|
|
|
|
|
|
eventMappings.forEach((mapping) => {
|
|
|
|
NativeAnimatedAPI.addAnimatedEventToView(viewTag, eventName, mapping);
|
|
|
|
});
|
|
|
|
|
|
|
|
return {
|
|
|
|
detach() {
|
2017-03-09 23:16:01 +00:00
|
|
|
eventMappings.forEach((mapping) => {
|
|
|
|
NativeAnimatedAPI.removeAnimatedEventFromView(
|
|
|
|
viewTag,
|
|
|
|
eventName,
|
|
|
|
mapping.animatedValueTag,
|
|
|
|
);
|
|
|
|
});
|
2017-03-02 23:09:39 +00:00
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-03-07 05:42:33 +00:00
|
|
|
function forkEvent(event: ?AnimatedEvent | ?Function, listener: Function): AnimatedEvent | Function {
|
|
|
|
if (!event) {
|
|
|
|
return listener;
|
|
|
|
} else if (event instanceof AnimatedEvent) {
|
|
|
|
event.__addListener(listener);
|
|
|
|
return event;
|
|
|
|
} else {
|
|
|
|
return (...args) => {
|
|
|
|
typeof event === 'function' && event(...args);
|
|
|
|
listener(...args);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function unforkEvent(event: ?AnimatedEvent | ?Function, listener: Function): void {
|
|
|
|
if (event && event instanceof AnimatedEvent) {
|
|
|
|
event.__removeListener(listener);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-19 11:07:36 +00:00
|
|
|
class AnimatedEvent {
|
|
|
|
_argMapping: Array<?Mapping>;
|
2017-03-07 05:42:33 +00:00
|
|
|
_listeners: Array<Function> = [];
|
2017-03-08 12:35:51 +00:00
|
|
|
_callListeners: Function;
|
2017-03-02 23:09:39 +00:00
|
|
|
_attachedEvent: ?{
|
|
|
|
detach: () => void,
|
|
|
|
};
|
2016-09-19 11:07:36 +00:00
|
|
|
__isNative: bool;
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
argMapping: Array<?Mapping>,
|
|
|
|
config?: EventConfig = {}
|
|
|
|
) {
|
|
|
|
this._argMapping = argMapping;
|
2017-03-07 05:42:33 +00:00
|
|
|
if (config.listener) {
|
|
|
|
this.__addListener(config.listener);
|
|
|
|
}
|
2017-03-08 12:35:51 +00:00
|
|
|
this._callListeners = this._callListeners.bind(this);
|
2017-03-02 23:09:39 +00:00
|
|
|
this._attachedEvent = null;
|
2016-11-02 06:55:21 +00:00
|
|
|
this.__isNative = shouldUseNativeDriver(config);
|
2016-09-19 11:07:36 +00:00
|
|
|
|
|
|
|
if (__DEV__) {
|
|
|
|
this._validateMapping();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-07 05:42:33 +00:00
|
|
|
__addListener(callback: Function): void {
|
|
|
|
this._listeners.push(callback);
|
|
|
|
}
|
|
|
|
|
|
|
|
__removeListener(callback: Function): void {
|
|
|
|
this._listeners = this._listeners.filter((listener) => listener !== callback);
|
|
|
|
}
|
|
|
|
|
2016-09-19 11:07:36 +00:00
|
|
|
__attach(viewRef, eventName) {
|
|
|
|
invariant(this.__isNative, 'Only native driven events need to be attached.');
|
|
|
|
|
2017-03-02 23:09:39 +00:00
|
|
|
this._attachedEvent = attachNativeEvent(viewRef, eventName, this._argMapping);
|
2016-09-19 11:07:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
__detach(viewTag, eventName) {
|
|
|
|
invariant(this.__isNative, 'Only native driven events need to be detached.');
|
|
|
|
|
2017-03-02 23:09:39 +00:00
|
|
|
this._attachedEvent && this._attachedEvent.detach();
|
2016-09-19 11:07:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
__getHandler() {
|
2017-02-16 08:18:33 +00:00
|
|
|
if (this.__isNative) {
|
2017-03-07 05:42:33 +00:00
|
|
|
return this._callListeners;
|
2017-02-16 08:18:33 +00:00
|
|
|
}
|
|
|
|
|
2016-09-19 11:07:36 +00:00
|
|
|
return (...args) => {
|
|
|
|
const traverse = (recMapping, recEvt, key) => {
|
|
|
|
if (typeof recEvt === 'number' && recMapping instanceof AnimatedValue) {
|
|
|
|
recMapping.setValue(recEvt);
|
|
|
|
} else if (typeof recMapping === 'object') {
|
|
|
|
for (const mappingKey in recMapping) {
|
|
|
|
traverse(recMapping[mappingKey], recEvt[mappingKey], mappingKey);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
if (!this.__isNative) {
|
|
|
|
this._argMapping.forEach((mapping, idx) => {
|
|
|
|
traverse(mapping, args[idx], 'arg' + idx);
|
|
|
|
});
|
|
|
|
}
|
2017-03-07 05:42:33 +00:00
|
|
|
this._callListeners(...args);
|
2016-09-19 11:07:36 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-03-08 12:35:51 +00:00
|
|
|
_callListeners(...args) {
|
2017-03-07 05:42:33 +00:00
|
|
|
this._listeners.forEach(listener => listener(...args));
|
2017-03-08 12:35:51 +00:00
|
|
|
}
|
2017-03-07 05:42:33 +00:00
|
|
|
|
2016-09-19 11:07:36 +00:00
|
|
|
_validateMapping() {
|
|
|
|
const traverse = (recMapping, recEvt, key) => {
|
2015-07-07 20:34:09 +00:00
|
|
|
if (typeof recEvt === 'number') {
|
|
|
|
invariant(
|
|
|
|
recMapping instanceof AnimatedValue,
|
|
|
|
'Bad mapping of type ' + typeof recMapping + ' for key ' + key +
|
|
|
|
', event value must map to AnimatedValue'
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
invariant(
|
|
|
|
typeof recMapping === 'object',
|
|
|
|
'Bad mapping of type ' + typeof recMapping + ' for key ' + key
|
|
|
|
);
|
|
|
|
invariant(
|
|
|
|
typeof recEvt === 'object',
|
|
|
|
'Bad event of type ' + typeof recEvt + ' for key ' + key
|
|
|
|
);
|
2016-09-19 11:07:36 +00:00
|
|
|
for (const mappingKey in recMapping) {
|
|
|
|
traverse(recMapping[mappingKey], recEvt[mappingKey], mappingKey);
|
2015-07-07 20:34:09 +00:00
|
|
|
}
|
|
|
|
};
|
2016-09-19 11:07:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var event = function(
|
|
|
|
argMapping: Array<?Mapping>,
|
|
|
|
config?: EventConfig,
|
|
|
|
): any {
|
|
|
|
const animatedEvent = new AnimatedEvent(argMapping, config);
|
|
|
|
if (animatedEvent.__isNative) {
|
|
|
|
return animatedEvent;
|
|
|
|
} else {
|
|
|
|
return animatedEvent.__getHandler();
|
|
|
|
}
|
2015-07-07 20:34:09 +00:00
|
|
|
};
|
|
|
|
|
2015-09-04 02:29:04 +00:00
|
|
|
/**
|
2017-02-17 22:41:23 +00:00
|
|
|
* The `Animated` library is designed to make animations fluid, powerful, and
|
|
|
|
* easy to build and maintain. `Animated` focuses on declarative relationships
|
|
|
|
* between inputs and outputs, with configurable transforms in between, and
|
|
|
|
* simple `start`/`stop` methods to control time-based animation execution.
|
2015-09-04 02:29:04 +00:00
|
|
|
*
|
2017-02-17 22:41:23 +00:00
|
|
|
* The simplest workflow for creating an animation is to to create an
|
|
|
|
* `Animated.Value`, hook it up to one or more style attributes of an animated
|
|
|
|
* component, and then drive updates via animations using `Animated.timing()`:
|
2015-09-04 02:29:04 +00:00
|
|
|
*
|
2017-02-17 22:41:23 +00:00
|
|
|
* ```javascript
|
|
|
|
* Animated.timing( // Animate value over time
|
|
|
|
* this.state.fadeAnim, // The value to drive
|
|
|
|
* {
|
|
|
|
* toValue: 1, // Animate to final value of 1
|
|
|
|
* }
|
|
|
|
* ).start(); // Start the animation
|
|
|
|
* ```
|
2015-09-04 02:29:04 +00:00
|
|
|
*
|
2017-02-17 22:41:23 +00:00
|
|
|
* Refer to the [Animations](docs/animations.html#animated-api) guide to see
|
|
|
|
* additional examples of `Animated` in action.
|
2015-09-04 02:29:04 +00:00
|
|
|
*
|
2017-02-17 22:41:23 +00:00
|
|
|
* ## Overview
|
2015-09-04 02:29:04 +00:00
|
|
|
*
|
2017-02-17 22:41:23 +00:00
|
|
|
* There are two value types you can use with `Animated`:
|
2015-09-04 02:29:04 +00:00
|
|
|
*
|
2017-02-17 22:41:23 +00:00
|
|
|
* - [`Animated.Value()`](docs/animated.html#value) for single values
|
|
|
|
* - [`Animated.ValueXY()`](docs/animated.html#valuexy) for vectors
|
2015-09-04 02:29:04 +00:00
|
|
|
*
|
2017-02-17 22:41:23 +00:00
|
|
|
* `Animated.Value` can bind to style properties or other props, and can be
|
|
|
|
* interpolated as well. A single `Animated.Value` can drive any number of
|
|
|
|
* properties.
|
|
|
|
*
|
|
|
|
* ### Configuring animations
|
|
|
|
*
|
|
|
|
* `Animated` provides three types of animation types. Each animation type
|
|
|
|
* provides a particular animation curve that controls how your values animate
|
|
|
|
* from their initial value to the final value:
|
|
|
|
*
|
|
|
|
* - [`Animated.decay()`](docs/animated.html#decay) starts with an initial
|
|
|
|
* velocity and gradually slows to a stop.
|
|
|
|
* - [`Animated.spring()`](docs/animated.html#spring) provides a simple
|
|
|
|
* spring physics model.
|
|
|
|
* - [`Animated.timing()`](docs/animated.html#timing) animates a value over time
|
|
|
|
* using [easing functions](docs/easing.html).
|
|
|
|
*
|
|
|
|
* In most cases, you will be using `timing()`. By default, it uses a symmetric
|
|
|
|
* easeInOut curve that conveys the gradual acceleration of an object to full
|
|
|
|
* speed and concludes by gradually decelerating to a stop.
|
|
|
|
*
|
|
|
|
* ### Working with animations
|
|
|
|
*
|
|
|
|
* Animations are started by calling `start()` on your animation. `start()`
|
|
|
|
* takes a completion callback that will be called when the animation is done.
|
|
|
|
* If the animation finished running normally, the completion callback will be
|
|
|
|
* invoked with `{finished: true}`. If the animation is done because `stop()`
|
|
|
|
* was called on it before it could finish (e.g. because it was interrupted by a
|
|
|
|
* gesture or another animation), then it will receive `{finished: false}`.
|
|
|
|
*
|
|
|
|
* ### Using the native driver
|
|
|
|
*
|
|
|
|
* By using the native driver, we send everything about the animation to native
|
|
|
|
* before starting the animation, allowing native code to perform the animation
|
|
|
|
* on the UI thread without having to go through the bridge on every frame.
|
|
|
|
* Once the animation has started, the JS thread can be blocked without
|
|
|
|
* affecting the animation.
|
|
|
|
*
|
|
|
|
* You can use the native driver by specifying `useNativeDriver: true` in your
|
|
|
|
* animation configuration. See the
|
|
|
|
* [Animations](docs/animations.html#using-the-native-driver) guide to learn
|
|
|
|
* more.
|
|
|
|
*
|
|
|
|
* ### Animatable components
|
|
|
|
*
|
|
|
|
* Only animatable components can be animated. These special components do the
|
|
|
|
* magic of binding the animated values to the properties, and do targeted
|
|
|
|
* 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.
|
2015-09-04 02:29:04 +00:00
|
|
|
*
|
2017-02-17 22:41:23 +00:00
|
|
|
* - [`createAnimatedComponent()`](docs/animated.html#createanimatedcomponent)
|
|
|
|
* can be used to make a component animatable.
|
2015-09-04 02:29:04 +00:00
|
|
|
*
|
2017-02-17 22:41:23 +00:00
|
|
|
* `Animated` exports the following animatable components using the above
|
|
|
|
* wrapper:
|
2015-09-04 02:29:04 +00:00
|
|
|
*
|
2017-02-17 22:41:23 +00:00
|
|
|
* - `Animated.Image`
|
|
|
|
* - `Animated.ScrollView`
|
|
|
|
* - `Animated.Text`
|
|
|
|
* - `Animated.View`
|
|
|
|
*
|
|
|
|
* ### Composing animations
|
|
|
|
*
|
|
|
|
* Animations can also be combined in complex ways using composition functions:
|
|
|
|
*
|
|
|
|
* - [`Animated.delay()`](docs/animated.html#delay) starts an animation after
|
|
|
|
* a given delay.
|
|
|
|
* - [`Animated.parallel()`](docs/animated.html#parallel) starts a number of
|
|
|
|
* animations at the same time.
|
|
|
|
* - [`Animated.sequence()`](docs/animated.html#sequence) starts the animations
|
|
|
|
* in order, waiting for each to complete before starting the next.
|
|
|
|
* - [`Animated.stagger()`](docs/animated.html#stagger) starts animations in
|
|
|
|
* order and in parallel, but with successive delays.
|
|
|
|
*
|
|
|
|
* Animations can also be chained together simply by setting the `toValue` of
|
|
|
|
* one animation to be another `Animated.Value`. See
|
|
|
|
* [Tracking dynamic values](docs/animations.html#tracking-dynamic-values) in
|
|
|
|
* the Animations guide.
|
|
|
|
*
|
|
|
|
* By default, if one animation is stopped or interrupted, then all other
|
|
|
|
* animations in the group are also stopped.
|
|
|
|
*
|
|
|
|
* ### Combining animated values
|
|
|
|
*
|
|
|
|
* You can combine two animated values via addition, multiplication, division,
|
|
|
|
* or modulo to make a new animated value:
|
|
|
|
*
|
|
|
|
* - [`Animated.add()`](docs/animated.html#add)
|
|
|
|
* - [`Animated.divide()`](docs/animated.html#divide)
|
|
|
|
* - [`Animated.modulo()`](docs/animated.html#modulo)
|
|
|
|
* - [`Animated.multiply()`](docs/animated.html#multiply)
|
|
|
|
*
|
|
|
|
* ### Interpolation
|
|
|
|
*
|
|
|
|
* The `interpolate()` function allows input ranges to map to different output
|
|
|
|
* ranges. By default, it will extrapolate the curve beyond the ranges given,
|
|
|
|
* but you can also have it clamp the output value. It uses lineal interpolation
|
|
|
|
* by default but also supports easing functions.
|
|
|
|
*
|
|
|
|
* - [`interpolate()`](docs/animated.html#interpolate)
|
|
|
|
*
|
|
|
|
* Read more about interpolation in the
|
|
|
|
* [Animation](docs/animations.html#interpolation) guide.
|
|
|
|
*
|
|
|
|
* ### Handling gestures and other events
|
|
|
|
*
|
|
|
|
* Gestures, like panning or scrolling, and other events can map directly to
|
|
|
|
* animated values using `Animated.event()`. This is done with a structured map
|
|
|
|
* syntax so that values can be extracted from complex event objects. The first
|
|
|
|
* level is an array to allow mapping across multiple args, and that array
|
|
|
|
* contains nested objects.
|
|
|
|
*
|
|
|
|
* - [`Animated.event()`](docs/animated.html#event)
|
|
|
|
*
|
|
|
|
* For example, when working with horizontal scrolling gestures, you would do
|
|
|
|
* the following in order to map `event.nativeEvent.contentOffset.x` to
|
|
|
|
* `scrollX` (an `Animated.Value`):
|
|
|
|
*
|
|
|
|
* ```javascript
|
|
|
|
* onScroll={Animated.event(
|
|
|
|
* // scrollX = e.nativeEvent.contentOffset.x
|
|
|
|
* [{ nativeEvent: {
|
|
|
|
* contentOffset: {
|
|
|
|
* x: scrollX
|
|
|
|
* }
|
|
|
|
* }
|
|
|
|
* }]
|
|
|
|
* )}
|
|
|
|
* ```
|
2015-09-04 02:29:04 +00:00
|
|
|
*
|
|
|
|
*/
|
2015-07-07 20:34:09 +00:00
|
|
|
module.exports = {
|
2015-09-04 02:29:04 +00:00
|
|
|
/**
|
|
|
|
* Standard value class for driving animations. Typically initialized with
|
|
|
|
* `new Animated.Value(0);`
|
2017-02-17 22:41:23 +00:00
|
|
|
*
|
|
|
|
* See also [`AnimatedValue`](docs/animated.html#animatedvalue).
|
2015-09-04 02:29:04 +00:00
|
|
|
*/
|
|
|
|
Value: AnimatedValue,
|
|
|
|
/**
|
|
|
|
* 2D value class for driving 2D animations, such as pan gestures.
|
2017-02-17 22:41:23 +00:00
|
|
|
*
|
|
|
|
* See also [`AnimatedValueXY`](docs/animated.html#animatedvaluexy).
|
2015-09-04 02:29:04 +00:00
|
|
|
*/
|
|
|
|
ValueXY: AnimatedValueXY,
|
2017-02-09 22:32:32 +00:00
|
|
|
/**
|
|
|
|
* exported to use the Interpolation type in flow
|
2017-02-17 22:41:23 +00:00
|
|
|
*
|
|
|
|
* See also [`AnimatedInterpolation`](docs/animated.html#animatedinterpolation).
|
2017-02-09 22:32:32 +00:00
|
|
|
*/
|
|
|
|
Interpolation: AnimatedInterpolation,
|
2015-07-07 20:34:09 +00:00
|
|
|
|
2015-09-04 02:29:04 +00:00
|
|
|
/**
|
|
|
|
* Animates a value from an initial velocity to zero based on a decay
|
|
|
|
* coefficient.
|
2017-02-17 22:41:23 +00:00
|
|
|
*
|
|
|
|
* Config is an object that may have the following options:
|
|
|
|
*
|
|
|
|
* - `velocity`: Initial velocity. Required.
|
|
|
|
* - `deceleration`: Rate of decay. Default 0.997.
|
|
|
|
* - `useNativeDriver`: Uses the native driver when true. Default false.
|
2015-09-04 02:29:04 +00:00
|
|
|
*/
|
2015-07-07 20:34:09 +00:00
|
|
|
decay,
|
2015-09-04 02:29:04 +00:00
|
|
|
/**
|
2017-02-17 22:41:23 +00:00
|
|
|
* Animates a value along a timed easing curve. The
|
|
|
|
* [`Easing`](docs/easing.html) module has tons of predefined curves, or you
|
|
|
|
* can use your own function.
|
|
|
|
*
|
|
|
|
* Config is an object that may have the following options:
|
|
|
|
*
|
|
|
|
* - `duration`: Length of animation (milliseconds). Default 500.
|
|
|
|
* - `easing`: Easing function to define curve.
|
|
|
|
* Default is `Easing.inOut(Easing.ease)`.
|
|
|
|
* - `delay`: Start the animation after delay (milliseconds). Default 0.
|
|
|
|
* - `useNativeDriver`: Uses the native driver when true. Default false.
|
2015-09-04 02:29:04 +00:00
|
|
|
*/
|
2015-07-07 20:34:09 +00:00
|
|
|
timing,
|
2015-09-04 02:29:04 +00:00
|
|
|
/**
|
2017-02-17 22:41:23 +00:00
|
|
|
* Spring animation based on Rebound and
|
|
|
|
* [Origami](https://facebook.github.io/origami/). Tracks velocity state to
|
2015-09-04 02:29:04 +00:00
|
|
|
* create fluid motions as the `toValue` updates, and can be chained together.
|
2017-02-17 22:41:23 +00:00
|
|
|
*
|
|
|
|
* Config is an object that may have the following options:
|
|
|
|
*
|
|
|
|
* - `friction`: Controls "bounciness"/overshoot. Default 7.
|
|
|
|
* - `tension`: Controls speed. Default 40.
|
|
|
|
* - `useNativeDriver`: Uses the native driver when true. Default false.
|
2015-09-04 02:29:04 +00:00
|
|
|
*/
|
2015-07-07 20:34:09 +00:00
|
|
|
spring,
|
|
|
|
|
2015-12-10 21:27:52 +00:00
|
|
|
/**
|
|
|
|
* Creates a new Animated value composed from two Animated values added
|
|
|
|
* together.
|
|
|
|
*/
|
|
|
|
add,
|
2016-09-26 23:32:27 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a new Animated value composed by dividing the first Animated value
|
|
|
|
* by the second Animated value.
|
|
|
|
*/
|
|
|
|
divide,
|
|
|
|
|
2015-12-10 21:27:52 +00:00
|
|
|
/**
|
|
|
|
* Creates a new Animated value composed from two Animated values multiplied
|
|
|
|
* together.
|
|
|
|
*/
|
|
|
|
multiply,
|
|
|
|
|
2016-02-26 01:18:27 +00:00
|
|
|
/**
|
|
|
|
* Creates a new Animated value that is the (non-negative) modulo of the
|
|
|
|
* provided Animated value
|
|
|
|
*/
|
|
|
|
modulo,
|
|
|
|
|
2016-08-23 20:59:07 +00:00
|
|
|
/**
|
|
|
|
* Create a new Animated value that is limited between 2 values. It uses the
|
|
|
|
* difference between the last value so even if the value is far from the bounds
|
|
|
|
* it will start changing when the value starts getting closer again.
|
|
|
|
* (`value = clamp(value + diff, min, max)`).
|
|
|
|
*
|
|
|
|
* This is useful with scroll events, for example, to show the navbar when
|
|
|
|
* scrolling up and to hide it when scrolling down.
|
|
|
|
*/
|
|
|
|
diffClamp,
|
|
|
|
|
2015-09-04 02:29:04 +00:00
|
|
|
/**
|
|
|
|
* 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,
|
Adds Animated.loop to Animated API
Summary:
* Any animation can be looped on the javascript thread
* Only basic animations supported natively at this stage, loops run
using the native driver cannot contain animations of type sequence,
parallel, stagger, or loop
Motivation: We need a spinner in our app that is displayed and animated while the javascript thread is tied up with other tasks. This means it needs to be offloaded from the javascript thread, so that it will continue to run while those tasks are churning away.
I originally submitted PR #9513, which has served our needs, but brentvatne pointed out a better way to do it. Had hoped his suggestion would be implemented by janicduplessis or another fb employee, but after 5 months I thought I'd give it another push.
I've put together an implementation that basically matches the suggested API. Let me know what you think, and whether others can pick it up from here and get it in to core.
Personal Motivation: I am leaving my current organisation on Feb 10th, so am trying to clean thing
Closes https://github.com/facebook/react-native/pull/11973
Differential Revision: D4704381
fbshipit-source-id: 42a2cdf5d53a7c0d08f86a58485f7f38739e6cd9
2017-03-14 06:49:47 +00:00
|
|
|
/**
|
|
|
|
* Loops a given animation continuously, so that each time it reaches the
|
|
|
|
* end, it resets and begins again from the start. Can specify number of
|
|
|
|
* times to loop using the key 'iterations' in the config. Will loop without
|
|
|
|
* blocking the UI thread if the child animation is set to 'useNativeDriver'.
|
|
|
|
*/
|
|
|
|
loop,
|
2015-09-04 02:29:04 +00:00
|
|
|
|
|
|
|
/**
|
2017-02-17 22:41:23 +00:00
|
|
|
* Takes an array of mappings and extracts values from each arg accordingly,
|
|
|
|
* then calls `setValue` on the mapped outputs. e.g.
|
2015-09-04 02:29:04 +00:00
|
|
|
*
|
|
|
|
*```javascript
|
2015-12-17 16:11:00 +00:00
|
|
|
* onScroll={Animated.event(
|
2015-09-04 02:29:04 +00:00
|
|
|
* [{nativeEvent: {contentOffset: {x: this._scrollX}}}]
|
|
|
|
* {listener}, // Optional async listener
|
|
|
|
* )
|
|
|
|
* ...
|
2015-12-17 16:11:00 +00:00
|
|
|
* onPanResponderMove: Animated.event([
|
2015-09-04 02:29:04 +00:00
|
|
|
* null, // raw event arg ignored
|
|
|
|
* {dx: this._panX}, // gestureState arg
|
|
|
|
* ]),
|
|
|
|
*```
|
2017-02-17 22:41:23 +00:00
|
|
|
*
|
|
|
|
* Config is an object that may have the following options:
|
|
|
|
*
|
|
|
|
* - `listener`: Optional async listener.
|
|
|
|
* - `useNativeDriver`: Uses the native driver when true. Default false.
|
2015-09-04 02:29:04 +00:00
|
|
|
*/
|
2015-07-07 20:34:09 +00:00
|
|
|
event,
|
|
|
|
|
2015-09-04 02:29:04 +00:00
|
|
|
/**
|
|
|
|
* Make any React component Animatable. Used to create `Animated.View`, etc.
|
|
|
|
*/
|
2015-07-07 20:34:09 +00:00
|
|
|
createAnimatedComponent,
|
2015-09-04 02:29:04 +00:00
|
|
|
|
2017-03-02 23:09:39 +00:00
|
|
|
/**
|
|
|
|
* Imperative API to attach an animated value to an event on a view. Prefer using
|
|
|
|
* `Animated.event` with `useNativeDrive: true` if possible.
|
|
|
|
*/
|
|
|
|
attachNativeEvent,
|
|
|
|
|
2017-03-07 05:42:33 +00:00
|
|
|
/**
|
|
|
|
* Advanced imperative API for snooping on animated events that are passed in through props. Use
|
|
|
|
* values directly where possible.
|
|
|
|
*/
|
|
|
|
forkEvent,
|
|
|
|
unforkEvent,
|
|
|
|
|
2015-09-04 02:29:04 +00:00
|
|
|
__PropsOnlyForTests: AnimatedProps,
|
2015-07-07 20:34:09 +00:00
|
|
|
};
|