Skip to content

Commit

Permalink
Initial non-native implementation of AnimatedColor
Browse files Browse the repository at this point in the history
Summary:
Creates Animated.Color for animating color props.

Implement AnimatedColor, which basically consists of 4 AnimatedValues (along the same vein as ValueXY) which allows us to just use AnimatedValue's interpolation. Provides a string color value of shape 'rgba(r, g, b, a)'

AnimationNode DAG looks like:

{F696076974}

Followup changes will include support for string color values, platform colors, and native driver.

Changelog:
[General][Added] - New Animated.Color node

Reviewed By: mdvacca

Differential Revision: D33778456

fbshipit-source-id: 83ddbc955156bf589c864f229a5f83fe6875fd0e
  • Loading branch information
genkikondo authored and facebook-github-bot committed Jan 27, 2022
1 parent 20b9ed9 commit ea90a76
Show file tree
Hide file tree
Showing 6 changed files with 351 additions and 10 deletions.
46 changes: 39 additions & 7 deletions Libraries/Animated/AnimatedImplementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import type {DecayAnimationConfig} from './animations/DecayAnimation';
import type {SpringAnimationConfig} from './animations/SpringAnimation';
import type {Mapping, EventConfig} from './AnimatedEvent';

import AnimatedColor from './nodes/AnimatedColor';

export type CompositeAnimation = {
start: (callback?: ?EndCallback) => void,
stop: () => void,
Expand Down Expand Up @@ -102,7 +104,7 @@ const _combineCallbacks = function (
};

const maybeVectorAnim = function (
value: AnimatedValue | AnimatedValueXY,
value: AnimatedValue | AnimatedValueXY | AnimatedColor,
config: Object,
anim: (value: AnimatedValue, config: Object) => CompositeAnimation,
): ?CompositeAnimation {
Expand All @@ -121,16 +123,42 @@ const maybeVectorAnim = function (
// 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});
} else if (value instanceof AnimatedColor) {
const configR = {...config};
const configG = {...config};
const configB = {...config};
const configA = {...config};
for (const key in config) {
const {r, g, b, a} = config[key];
if (
r !== undefined &&
g !== undefined &&
b !== undefined &&
a !== undefined
) {
configR[key] = r;
configG[key] = g;
configB[key] = b;
configA[key] = a;
}
}
const aR = anim((value: AnimatedColor).r, configR);
const aG = anim((value: AnimatedColor).g, configG);
const aB = anim((value: AnimatedColor).b, configB);
const aA = anim((value: AnimatedColor).a, configA);
// We use `stopTogether: false` here because otherwise tracking will break
// because the second animation will get stopped before it can update.
return parallel([aR, aG, aB, aA], {stopTogether: false});
}
return null;
};

const spring = function (
value: AnimatedValue | AnimatedValueXY,
value: AnimatedValue | AnimatedValueXY | AnimatedColor,
config: SpringAnimationConfig,
): CompositeAnimation {
const start = function (
animatedValue: AnimatedValue | AnimatedValueXY,
animatedValue: AnimatedValue | AnimatedValueXY | AnimatedColor,
configuration: SpringAnimationConfig,
callback?: ?EndCallback,
): void {
Expand Down Expand Up @@ -179,11 +207,11 @@ const spring = function (
};

const timing = function (
value: AnimatedValue | AnimatedValueXY,
value: AnimatedValue | AnimatedValueXY | AnimatedColor,
config: TimingAnimationConfig,
): CompositeAnimation {
const start = function (
animatedValue: AnimatedValue | AnimatedValueXY,
animatedValue: AnimatedValue | AnimatedValueXY | AnimatedColor,
configuration: TimingAnimationConfig,
callback?: ?EndCallback,
): void {
Expand Down Expand Up @@ -233,11 +261,11 @@ const timing = function (
};

const decay = function (
value: AnimatedValue | AnimatedValueXY,
value: AnimatedValue | AnimatedValueXY | AnimatedColor,
config: DecayAnimationConfig,
): CompositeAnimation {
const start = function (
animatedValue: AnimatedValue | AnimatedValueXY,
animatedValue: AnimatedValue | AnimatedValueXY | AnimatedColor,
configuration: DecayAnimationConfig,
callback?: ?EndCallback,
): void {
Expand Down Expand Up @@ -547,6 +575,10 @@ module.exports = {
* See https://reactnative.dev/docs/animatedvaluexy
*/
ValueXY: AnimatedValueXY,
/**
* Value class for driving color animations.
*/
Color: AnimatedColor,
/**
* Exported to use the Interpolation type in flow.
*
Expand Down
9 changes: 6 additions & 3 deletions Libraries/Animated/AnimatedMock.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import type {TimingAnimationConfig} from './animations/TimingAnimation';
import type {DecayAnimationConfig} from './animations/DecayAnimation';
import type {SpringAnimationConfig} from './animations/SpringAnimation';

import AnimatedColor from './nodes/AnimatedColor';

/**
* Animations are a source of flakiness in snapshot testing. This mock replaces
* animation functions from AnimatedImplementation with empty animations for
Expand Down Expand Up @@ -89,7 +91,7 @@ const mockCompositeAnimation = (
});

const spring = function (
value: AnimatedValue | AnimatedValueXY,
value: AnimatedValue | AnimatedValueXY | AnimatedColor,
config: SpringAnimationConfig,
): CompositeAnimation {
const anyValue: any = value;
Expand All @@ -103,7 +105,7 @@ const spring = function (
};

const timing = function (
value: AnimatedValue | AnimatedValueXY,
value: AnimatedValue | AnimatedValueXY | AnimatedColor,
config: TimingAnimationConfig,
): CompositeAnimation {
const anyValue: any = value;
Expand All @@ -117,7 +119,7 @@ const timing = function (
};

const decay = function (
value: AnimatedValue | AnimatedValueXY,
value: AnimatedValue | AnimatedValueXY | AnimatedColor,
config: DecayAnimationConfig,
): CompositeAnimation {
return emptyAnimation;
Expand Down Expand Up @@ -164,6 +166,7 @@ const loop = function (
module.exports = {
Value: AnimatedValue,
ValueXY: AnimatedValueXY,
Color: AnimatedColor,
Interpolation: AnimatedInterpolation,
Node: AnimatedNode,
decay,
Expand Down
80 changes: 80 additions & 0 deletions Libraries/Animated/__tests__/Animated-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -972,4 +972,84 @@ describe('Animated tests', () => {
}
});
});

describe('Animated Colors', () => {
it('should animate colors', () => {
const color = new Animated.Color({r: 255, g: 0, b: 0, a: 1.0});
const callback = jest.fn();
const node = new AnimatedProps(
{
style: {
backgroundColor: color,
transform: [
{
scale: color.a.interpolate({
inputRange: [0, 1],
outputRange: [1, 2],
}),
},
],
},
},
callback,
);

expect(node.__getValue()).toEqual({
style: {
backgroundColor: 'rgba(255, 0, 0, 1)',
transform: [{scale: 2}],
},
});

node.__attach();
expect(callback.mock.calls.length).toBe(0);

color.setValue({r: 11, g: 22, b: 33, a: 0.5});
expect(callback.mock.calls.length).toBe(4);
expect(node.__getValue()).toEqual({
style: {
backgroundColor: 'rgba(11, 22, 33, 0.5)',
transform: [{scale: 1.5}],
},
});

node.__detach();
color.setValue({r: 255, g: 0, b: 0, a: 1.0});
expect(callback.mock.calls.length).toBe(4);
});

it('should track colors', () => {
const color1 = new Animated.Color();
const color2 = new Animated.Color();
Animated.timing(color2, {
toValue: color1,
duration: 0,
useNativeDriver: false,
}).start();
color1.setValue({r: 11, g: 22, b: 33, a: 0.5});
expect(color2.__getValue()).toEqual('rgba(11, 22, 33, 0.5)');

// Make sure tracking keeps working (see stopTogether in ParallelConfig used
// by maybeVectorAnim).
color1.setValue({r: 255, g: 0, b: 0, a: 1.0});
expect(color2.__getValue()).toEqual('rgba(255, 0, 0, 1)');
});

it('should track with springs', () => {
const color1 = new Animated.Color();
const color2 = new Animated.Color();
Animated.spring(color2, {
toValue: color1,
tension: 3000, // faster spring for faster test
friction: 60,
useNativeDriver: false,
}).start();
color1.setValue({r: 11, g: 22, b: 33, a: 0.5});
jest.runAllTimers();
expect(color2.__getValue()).toEqual('rgba(11, 22, 33, 0.5)');
color1.setValue({r: 44, g: 55, b: 66, a: 0.0});
jest.runAllTimers();
expect(color2.__getValue()).toEqual('rgba(44, 55, 66, 0)');
});
});
});
10 changes: 10 additions & 0 deletions Libraries/Animated/animations/SpringAnimation.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ const {shouldUseNativeDriver} = require('../NativeAnimatedHelper');
import type {PlatformConfig} from '../AnimatedPlatformConfig';
import type {AnimationConfig, EndCallback} from './Animation';

import AnimatedColor from '../nodes/AnimatedColor';

export type SpringAnimationConfig = {
...AnimationConfig,
toValue:
Expand All @@ -34,6 +36,14 @@ export type SpringAnimationConfig = {
...
}
| AnimatedValueXY
| {
r: number,
g: number,
b: number,
a: number,
...
}
| AnimatedColor
| AnimatedInterpolation,
overshootClamping?: boolean,
restDisplacementThreshold?: number,
Expand Down
10 changes: 10 additions & 0 deletions Libraries/Animated/animations/TimingAnimation.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ const {shouldUseNativeDriver} = require('../NativeAnimatedHelper');
import type {PlatformConfig} from '../AnimatedPlatformConfig';
import type {AnimationConfig, EndCallback} from './Animation';

import AnimatedColor from '../nodes/AnimatedColor';

export type TimingAnimationConfig = {
...AnimationConfig,
toValue:
Expand All @@ -31,6 +33,14 @@ export type TimingAnimationConfig = {
...
}
| AnimatedValueXY
| {
r: number,
g: number,
b: number,
a: number,
...
}
| AnimatedColor
| AnimatedInterpolation,
easing?: (value: number) => number,
duration?: number,
Expand Down
Loading

0 comments on commit ea90a76

Please sign in to comment.