Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for using animated values as the target of an interpolation. #18029

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
41 changes: 41 additions & 0 deletions Libraries/Animated/src/AnimatedImplementation.js
Expand Up @@ -39,6 +39,34 @@ import type {TimingAnimationConfig} from './animations/TimingAnimation';
import type {DecayAnimationConfig} from './animations/DecayAnimation';
import type {SpringAnimationConfig} from './animations/SpringAnimation';
import type {Mapping, EventConfig} from './AnimatedEvent';
import type {InterpolationConfigType} from './nodes/AnimatedInterpolation';

const interpolateMethod = function(
config: InterpolationConfigType,
): AnimatedInterpolation {
console.warn(
'The animation.interpolate(config) method will be removed from animated nodes in favour of Animated.interpolate(animation, config).',
);
return new AnimatedInterpolation(this, config);
};

// To avoid some code duplication and a circular dependency we
// are adding the interpolate method directly onto these prototypes.
// This should eventually be removed.
//$FlowFixMe
AnimatedAddition.prototype.interpolate = interpolateMethod;
//$FlowFixMe
AnimatedDiffClamp.prototype.interpolate = interpolateMethod;
//$FlowFixMe
AnimatedDivision.prototype.interpolate = interpolateMethod;
//$FlowFixMe
AnimatedInterpolation.prototype.interpolate = interpolateMethod;
//$FlowFixMe
AnimatedModulo.prototype.interpolate = interpolateMethod;
//$FlowFixMe
AnimatedMultiplication.prototype.interpolate = interpolateMethod;
//$FlowFixMe
AnimatedValue.prototype.interpolate = interpolateMethod;

export type CompositeAnimation = {
start: (callback?: ?EndCallback) => void,
Expand Down Expand Up @@ -233,6 +261,13 @@ const timing = function(
);
};

const interpolate = function(
value: AnimatedValue,
config: InterpolationConfigType,
): AnimatedInterpolation {
return new AnimatedInterpolation(value, config);
};

const decay = function(
value: AnimatedValue | AnimatedValueXY,
config: DecayAnimationConfig,
Expand Down Expand Up @@ -546,6 +581,12 @@ module.exports = {
*/
Node: AnimatedNode,

/**
* Interpolates the value before updating the property, e.g. mapping 0-1 to
* 0-10.
*/
interpolate,

/**
* Animates a value from an initial velocity to zero based on a decay
* coefficient.
Expand Down
3 changes: 2 additions & 1 deletion Libraries/Animated/src/__tests__/AnimatedNative-test.js
Expand Up @@ -475,8 +475,9 @@ describe('Native Animated', () => {
expect(nativeAnimatedModule.createAnimatedNode)
.toBeCalledWith(expect.any(Number), {
type: 'interpolation',
parent: expect.any(Number),
inputRange: [10, 20],
outputRange: [0, 1],
outputRange: [expect.any(Number), expect.any(Number)],
extrapolateLeft: 'extend',
extrapolateRight: 'extend',
});
Expand Down
7 changes: 0 additions & 7 deletions Libraries/Animated/src/nodes/AnimatedAddition.js
Expand Up @@ -10,13 +10,10 @@
*/
'use strict';

const AnimatedInterpolation = require('./AnimatedInterpolation');
const AnimatedNode = require('./AnimatedNode');
const AnimatedValue = require('./AnimatedValue');
const AnimatedWithChildren = require('./AnimatedWithChildren');

import type {InterpolationConfigType} from './AnimatedInterpolation';

class AnimatedAddition extends AnimatedWithChildren {
_a: AnimatedNode;
_b: AnimatedNode;
Expand All @@ -37,10 +34,6 @@ class AnimatedAddition extends AnimatedWithChildren {
return this._a.__getValue() + this._b.__getValue();
}

interpolate(config: InterpolationConfigType): AnimatedInterpolation {
return new AnimatedInterpolation(this, config);
}

__attach(): void {
this._a.__addChild(this);
this._b.__addChild(this);
Expand Down
7 changes: 0 additions & 7 deletions Libraries/Animated/src/nodes/AnimatedDiffClamp.js
Expand Up @@ -10,12 +10,9 @@
*/
'use strict';

const AnimatedInterpolation = require('./AnimatedInterpolation');
const AnimatedNode = require('./AnimatedNode');
const AnimatedWithChildren = require('./AnimatedWithChildren');

import type {InterpolationConfigType} from './AnimatedInterpolation';

class AnimatedDiffClamp extends AnimatedWithChildren {
_a: AnimatedNode;
_min: number;
Expand All @@ -37,10 +34,6 @@ class AnimatedDiffClamp extends AnimatedWithChildren {
super.__makeNative();
}

interpolate(config: InterpolationConfigType): AnimatedInterpolation {
return new AnimatedInterpolation(this, config);
}

__getValue(): number {
const value = this._a.__getValue();
const diff = value - this._lastValue;
Expand Down
7 changes: 0 additions & 7 deletions Libraries/Animated/src/nodes/AnimatedDivision.js
Expand Up @@ -10,13 +10,10 @@
*/
'use strict';

const AnimatedInterpolation = require('./AnimatedInterpolation');
const AnimatedNode = require('./AnimatedNode');
const AnimatedValue = require('./AnimatedValue');
const AnimatedWithChildren = require('./AnimatedWithChildren');

import type {InterpolationConfigType} from './AnimatedInterpolation';

class AnimatedDivision extends AnimatedWithChildren {
_a: AnimatedNode;
_b: AnimatedNode;
Expand All @@ -42,10 +39,6 @@ class AnimatedDivision extends AnimatedWithChildren {
return a / b;
}

interpolate(config: InterpolationConfigType): AnimatedInterpolation {
return new AnimatedInterpolation(this, config);
}

__attach(): void {
this._a.__addChild(this);
this._b.__addChild(this);
Expand Down
91 changes: 62 additions & 29 deletions Libraries/Animated/src/nodes/AnimatedInterpolation.js
Expand Up @@ -12,6 +12,7 @@
'use strict';

const AnimatedNode = require('./AnimatedNode');
const AnimatedValue = require('./AnimatedValue');
const AnimatedWithChildren = require('./AnimatedWithChildren');
const NativeAnimatedHelper = require('../NativeAnimatedHelper');

Expand All @@ -26,7 +27,7 @@ export type InterpolationConfigType = {
* detected during the deployment of v0.38.0. To see the error, remove this
* comment and run flow
*/
outputRange: Array<number> | Array<string>,
outputRange: Array<number> | Array<string> | Array<AnimatedNode>,
easing?: (input: number) => number,
extrapolate?: ExtrapolateType,
extrapolateLeft?: ExtrapolateType,
Expand All @@ -46,7 +47,7 @@ function createInterpolation(
return createInterpolationFromStringOutputRange(config);
}

const outputRange: Array<number> = (config.outputRange: any);
const outputRange: Array<number> | Array<AnimatedNode> = config.outputRange;
checkInfiniteRange('outputRange', outputRange);

const inputRange = config.inputRange;
Expand Down Expand Up @@ -85,12 +86,20 @@ function createInterpolation(
);

const range = findRange(input, inputRange);
const outputStart: number | AnimatedNode = outputRange[range];
const outputEnd: number | AnimatedNode = outputRange[range + 1];
const outputStartValue =
outputStart instanceof AnimatedNode
? outputStart.__getValue()
: outputStart;
const outputEndValue =
outputEnd instanceof AnimatedNode ? outputEnd.__getValue() : outputEnd;
return interpolate(
input,
inputRange[range],
inputRange[range + 1],
outputRange[range],
outputRange[range + 1],
outputStartValue,
outputEndValue,
easing,
extrapolateLeft,
extrapolateRight,
Expand Down Expand Up @@ -291,7 +300,7 @@ function checkValidInputRange(arr: Array<number>) {
}
}

function checkInfiniteRange(name: string, arr: Array<number>) {
function checkInfiniteRange(name: string, arr: Array<any>) {
invariant(arr.length >= 2, name + ' must have at least 2 elements');
invariant(
arr.length !== 2 || arr[0] !== -Infinity || arr[1] !== Infinity,
Expand All @@ -311,17 +320,24 @@ class AnimatedInterpolation extends AnimatedWithChildren {

_parent: AnimatedNode;
_config: InterpolationConfigType;
_transformedOutputRange: Array<AnimatedNode>;
_interpolation: (input: number) => number | string;

constructor(parent: AnimatedNode, config: InterpolationConfigType) {
super();
this._parent = parent;
this._config = config;
this._transformedOutputRange = this.__transformOutputRangeToAnimatedValues(
config.outputRange,
);
this._interpolation = createInterpolation(config);
}

__makeNative() {
__makeNative(): void {
this._parent.__makeNative();
this._transformedOutputRange.forEach(function(value) {
value.__makeNative();
});
super.__makeNative();
}

Expand All @@ -334,37 +350,54 @@ class AnimatedInterpolation extends AnimatedWithChildren {
return this._interpolation(parentValue);
}

interpolate(config: InterpolationConfigType): AnimatedInterpolation {
return new AnimatedInterpolation(this, config);
}

__attach(): void {
this._parent.__addChild(this);
const that = this;
this._parent.__addChild(that);
this._transformedOutputRange.forEach(function(value) {
value.__addChild(that);
});
}

__detach(): void {
this._parent.__removeChild(this);
const that = this;
this._parent.__removeChild(that);
this._transformedOutputRange.forEach(function(value) {
value.__removeChild(that);
});
super.__detach();
}

__transformDataType(range: Array<any>) {
// Change the string array type to number array
// So we can reuse the same logic in iOS and Android platform
/* $FlowFixMe(>=0.70.0 site=react_native_fb) This comment suppresses an
* error found when Flow v0.70 was deployed. To see the error delete this
* comment and run Flow. */
__transformOutputRangeToAnimatedValues(
range: Array<number | string | AnimatedNode>,
): Array<AnimatedNode> {
return range.map(function(value) {
if (typeof value !== 'string') {
return value;
}
if (/deg$/.test(value)) {
if (typeof value === 'string' && /deg$/.test(value)) {
const degrees = parseFloat(value) || 0;
// Radians.
const radians = degrees * Math.PI / 180.0;
return radians;
} else {
// Assume radians
return parseFloat(value) || 0;
return new AnimatedValue(radians);
}
if (typeof value === 'string') {
// Assume radians.
const radians = parseFloat(value) || 0;
return new AnimatedValue(radians);
}
if (typeof value === 'number') {
// Just a plain number value.
return new AnimatedValue(value);
}
if (value instanceof AnimatedNode) {
return value;
}
throw new Error('Incompatible type passed to outputRange.');
});
}

__outputRangeToTags(range: Array<AnimatedNode>): Array<number> {
return range.map(function(value) {
const tag = value.__getNativeTag();
invariant(tag, 'There must be a native tag for this value.');
return tag;
});
}

Expand All @@ -374,14 +407,14 @@ class AnimatedInterpolation extends AnimatedWithChildren {
}

return {
type: 'interpolation',
parent: this._parent.__getNativeTag(),
inputRange: this._config.inputRange,
// Only the `outputRange` can contain strings so we don't need to transform `inputRange` here
outputRange: this.__transformDataType(this._config.outputRange),
outputRange: this.__outputRangeToTags(this._transformedOutputRange),
extrapolateLeft:
this._config.extrapolateLeft || this._config.extrapolate || 'extend',
extrapolateRight:
this._config.extrapolateRight || this._config.extrapolate || 'extend',
type: 'interpolation',
};
}
}
Expand Down
7 changes: 0 additions & 7 deletions Libraries/Animated/src/nodes/AnimatedModulo.js
Expand Up @@ -10,12 +10,9 @@
*/
'use strict';

const AnimatedInterpolation = require('./AnimatedInterpolation');
const AnimatedNode = require('./AnimatedNode');
const AnimatedWithChildren = require('./AnimatedWithChildren');

import type {InterpolationConfigType} from './AnimatedInterpolation';

class AnimatedModulo extends AnimatedWithChildren {
_a: AnimatedNode;
_modulus: number;
Expand All @@ -37,10 +34,6 @@ class AnimatedModulo extends AnimatedWithChildren {
);
}

interpolate(config: InterpolationConfigType): AnimatedInterpolation {
return new AnimatedInterpolation(this, config);
}

__attach(): void {
this._a.__addChild(this);
}
Expand Down
7 changes: 0 additions & 7 deletions Libraries/Animated/src/nodes/AnimatedMultiplication.js
Expand Up @@ -10,13 +10,10 @@
*/
'use strict';

const AnimatedInterpolation = require('./AnimatedInterpolation');
const AnimatedNode = require('./AnimatedNode');
const AnimatedValue = require('./AnimatedValue');
const AnimatedWithChildren = require('./AnimatedWithChildren');

import type {InterpolationConfigType} from './AnimatedInterpolation';

class AnimatedMultiplication extends AnimatedWithChildren {
_a: AnimatedNode;
_b: AnimatedNode;
Expand All @@ -37,10 +34,6 @@ class AnimatedMultiplication extends AnimatedWithChildren {
return this._a.__getValue() * this._b.__getValue();
}

interpolate(config: InterpolationConfigType): AnimatedInterpolation {
return new AnimatedInterpolation(this, config);
}

__attach(): void {
this._a.__addChild(this);
this._b.__addChild(this);
Expand Down
16 changes: 16 additions & 0 deletions Libraries/Animated/src/nodes/AnimatedNode.js
Expand Up @@ -34,6 +34,22 @@ class AnimatedNode {
return [];
}

/**
* Deprecated - Use `Animated.interpolate(animation, config)` instead.
*
* Interpolates the value before updating the property, e.g. mapping 0-1 to
* 0-10. Not available on all node types.
*
* @deprecated
*/
interpolate(config: any): AnimatedNode {
throw new Error(
'This node type does not implement an interpolate method,' +
' the interpolate method will be removed from all nodes' +
' in favour of Animated.interpolate(animation, config).',
);
}

/* Methods and props used by native Animated impl */
__isNative: boolean;
__nativeTag: ?number;
Expand Down