Skip to content

Commit ea90a76

Browse files
genkikondofacebook-github-bot
authored andcommitted
Initial non-native implementation of AnimatedColor
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
1 parent 20b9ed9 commit ea90a76

File tree

6 files changed

+351
-10
lines changed

6 files changed

+351
-10
lines changed

Libraries/Animated/AnimatedImplementation.js

+39-7
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ import type {DecayAnimationConfig} from './animations/DecayAnimation';
3838
import type {SpringAnimationConfig} from './animations/SpringAnimation';
3939
import type {Mapping, EventConfig} from './AnimatedEvent';
4040

41+
import AnimatedColor from './nodes/AnimatedColor';
42+
4143
export type CompositeAnimation = {
4244
start: (callback?: ?EndCallback) => void,
4345
stop: () => void,
@@ -102,7 +104,7 @@ const _combineCallbacks = function (
102104
};
103105

104106
const maybeVectorAnim = function (
105-
value: AnimatedValue | AnimatedValueXY,
107+
value: AnimatedValue | AnimatedValueXY | AnimatedColor,
106108
config: Object,
107109
anim: (value: AnimatedValue, config: Object) => CompositeAnimation,
108110
): ?CompositeAnimation {
@@ -121,16 +123,42 @@ const maybeVectorAnim = function (
121123
// We use `stopTogether: false` here because otherwise tracking will break
122124
// because the second animation will get stopped before it can update.
123125
return parallel([aX, aY], {stopTogether: false});
126+
} else if (value instanceof AnimatedColor) {
127+
const configR = {...config};
128+
const configG = {...config};
129+
const configB = {...config};
130+
const configA = {...config};
131+
for (const key in config) {
132+
const {r, g, b, a} = config[key];
133+
if (
134+
r !== undefined &&
135+
g !== undefined &&
136+
b !== undefined &&
137+
a !== undefined
138+
) {
139+
configR[key] = r;
140+
configG[key] = g;
141+
configB[key] = b;
142+
configA[key] = a;
143+
}
144+
}
145+
const aR = anim((value: AnimatedColor).r, configR);
146+
const aG = anim((value: AnimatedColor).g, configG);
147+
const aB = anim((value: AnimatedColor).b, configB);
148+
const aA = anim((value: AnimatedColor).a, configA);
149+
// We use `stopTogether: false` here because otherwise tracking will break
150+
// because the second animation will get stopped before it can update.
151+
return parallel([aR, aG, aB, aA], {stopTogether: false});
124152
}
125153
return null;
126154
};
127155

128156
const spring = function (
129-
value: AnimatedValue | AnimatedValueXY,
157+
value: AnimatedValue | AnimatedValueXY | AnimatedColor,
130158
config: SpringAnimationConfig,
131159
): CompositeAnimation {
132160
const start = function (
133-
animatedValue: AnimatedValue | AnimatedValueXY,
161+
animatedValue: AnimatedValue | AnimatedValueXY | AnimatedColor,
134162
configuration: SpringAnimationConfig,
135163
callback?: ?EndCallback,
136164
): void {
@@ -179,11 +207,11 @@ const spring = function (
179207
};
180208

181209
const timing = function (
182-
value: AnimatedValue | AnimatedValueXY,
210+
value: AnimatedValue | AnimatedValueXY | AnimatedColor,
183211
config: TimingAnimationConfig,
184212
): CompositeAnimation {
185213
const start = function (
186-
animatedValue: AnimatedValue | AnimatedValueXY,
214+
animatedValue: AnimatedValue | AnimatedValueXY | AnimatedColor,
187215
configuration: TimingAnimationConfig,
188216
callback?: ?EndCallback,
189217
): void {
@@ -233,11 +261,11 @@ const timing = function (
233261
};
234262

235263
const decay = function (
236-
value: AnimatedValue | AnimatedValueXY,
264+
value: AnimatedValue | AnimatedValueXY | AnimatedColor,
237265
config: DecayAnimationConfig,
238266
): CompositeAnimation {
239267
const start = function (
240-
animatedValue: AnimatedValue | AnimatedValueXY,
268+
animatedValue: AnimatedValue | AnimatedValueXY | AnimatedColor,
241269
configuration: DecayAnimationConfig,
242270
callback?: ?EndCallback,
243271
): void {
@@ -547,6 +575,10 @@ module.exports = {
547575
* See https://reactnative.dev/docs/animatedvaluexy
548576
*/
549577
ValueXY: AnimatedValueXY,
578+
/**
579+
* Value class for driving color animations.
580+
*/
581+
Color: AnimatedColor,
550582
/**
551583
* Exported to use the Interpolation type in flow.
552584
*

Libraries/Animated/AnimatedMock.js

+6-3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import type {TimingAnimationConfig} from './animations/TimingAnimation';
2424
import type {DecayAnimationConfig} from './animations/DecayAnimation';
2525
import type {SpringAnimationConfig} from './animations/SpringAnimation';
2626

27+
import AnimatedColor from './nodes/AnimatedColor';
28+
2729
/**
2830
* Animations are a source of flakiness in snapshot testing. This mock replaces
2931
* animation functions from AnimatedImplementation with empty animations for
@@ -89,7 +91,7 @@ const mockCompositeAnimation = (
8991
});
9092

9193
const spring = function (
92-
value: AnimatedValue | AnimatedValueXY,
94+
value: AnimatedValue | AnimatedValueXY | AnimatedColor,
9395
config: SpringAnimationConfig,
9496
): CompositeAnimation {
9597
const anyValue: any = value;
@@ -103,7 +105,7 @@ const spring = function (
103105
};
104106

105107
const timing = function (
106-
value: AnimatedValue | AnimatedValueXY,
108+
value: AnimatedValue | AnimatedValueXY | AnimatedColor,
107109
config: TimingAnimationConfig,
108110
): CompositeAnimation {
109111
const anyValue: any = value;
@@ -117,7 +119,7 @@ const timing = function (
117119
};
118120

119121
const decay = function (
120-
value: AnimatedValue | AnimatedValueXY,
122+
value: AnimatedValue | AnimatedValueXY | AnimatedColor,
121123
config: DecayAnimationConfig,
122124
): CompositeAnimation {
123125
return emptyAnimation;
@@ -164,6 +166,7 @@ const loop = function (
164166
module.exports = {
165167
Value: AnimatedValue,
166168
ValueXY: AnimatedValueXY,
169+
Color: AnimatedColor,
167170
Interpolation: AnimatedInterpolation,
168171
Node: AnimatedNode,
169172
decay,

Libraries/Animated/__tests__/Animated-test.js

+80
Original file line numberDiff line numberDiff line change
@@ -972,4 +972,84 @@ describe('Animated tests', () => {
972972
}
973973
});
974974
});
975+
976+
describe('Animated Colors', () => {
977+
it('should animate colors', () => {
978+
const color = new Animated.Color({r: 255, g: 0, b: 0, a: 1.0});
979+
const callback = jest.fn();
980+
const node = new AnimatedProps(
981+
{
982+
style: {
983+
backgroundColor: color,
984+
transform: [
985+
{
986+
scale: color.a.interpolate({
987+
inputRange: [0, 1],
988+
outputRange: [1, 2],
989+
}),
990+
},
991+
],
992+
},
993+
},
994+
callback,
995+
);
996+
997+
expect(node.__getValue()).toEqual({
998+
style: {
999+
backgroundColor: 'rgba(255, 0, 0, 1)',
1000+
transform: [{scale: 2}],
1001+
},
1002+
});
1003+
1004+
node.__attach();
1005+
expect(callback.mock.calls.length).toBe(0);
1006+
1007+
color.setValue({r: 11, g: 22, b: 33, a: 0.5});
1008+
expect(callback.mock.calls.length).toBe(4);
1009+
expect(node.__getValue()).toEqual({
1010+
style: {
1011+
backgroundColor: 'rgba(11, 22, 33, 0.5)',
1012+
transform: [{scale: 1.5}],
1013+
},
1014+
});
1015+
1016+
node.__detach();
1017+
color.setValue({r: 255, g: 0, b: 0, a: 1.0});
1018+
expect(callback.mock.calls.length).toBe(4);
1019+
});
1020+
1021+
it('should track colors', () => {
1022+
const color1 = new Animated.Color();
1023+
const color2 = new Animated.Color();
1024+
Animated.timing(color2, {
1025+
toValue: color1,
1026+
duration: 0,
1027+
useNativeDriver: false,
1028+
}).start();
1029+
color1.setValue({r: 11, g: 22, b: 33, a: 0.5});
1030+
expect(color2.__getValue()).toEqual('rgba(11, 22, 33, 0.5)');
1031+
1032+
// Make sure tracking keeps working (see stopTogether in ParallelConfig used
1033+
// by maybeVectorAnim).
1034+
color1.setValue({r: 255, g: 0, b: 0, a: 1.0});
1035+
expect(color2.__getValue()).toEqual('rgba(255, 0, 0, 1)');
1036+
});
1037+
1038+
it('should track with springs', () => {
1039+
const color1 = new Animated.Color();
1040+
const color2 = new Animated.Color();
1041+
Animated.spring(color2, {
1042+
toValue: color1,
1043+
tension: 3000, // faster spring for faster test
1044+
friction: 60,
1045+
useNativeDriver: false,
1046+
}).start();
1047+
color1.setValue({r: 11, g: 22, b: 33, a: 0.5});
1048+
jest.runAllTimers();
1049+
expect(color2.__getValue()).toEqual('rgba(11, 22, 33, 0.5)');
1050+
color1.setValue({r: 44, g: 55, b: 66, a: 0.0});
1051+
jest.runAllTimers();
1052+
expect(color2.__getValue()).toEqual('rgba(44, 55, 66, 0)');
1053+
});
1054+
});
9751055
});

Libraries/Animated/animations/SpringAnimation.js

+10
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ const {shouldUseNativeDriver} = require('../NativeAnimatedHelper');
2323
import type {PlatformConfig} from '../AnimatedPlatformConfig';
2424
import type {AnimationConfig, EndCallback} from './Animation';
2525

26+
import AnimatedColor from '../nodes/AnimatedColor';
27+
2628
export type SpringAnimationConfig = {
2729
...AnimationConfig,
2830
toValue:
@@ -34,6 +36,14 @@ export type SpringAnimationConfig = {
3436
...
3537
}
3638
| AnimatedValueXY
39+
| {
40+
r: number,
41+
g: number,
42+
b: number,
43+
a: number,
44+
...
45+
}
46+
| AnimatedColor
3747
| AnimatedInterpolation,
3848
overshootClamping?: boolean,
3949
restDisplacementThreshold?: number,

Libraries/Animated/animations/TimingAnimation.js

+10
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ const {shouldUseNativeDriver} = require('../NativeAnimatedHelper');
2020
import type {PlatformConfig} from '../AnimatedPlatformConfig';
2121
import type {AnimationConfig, EndCallback} from './Animation';
2222

23+
import AnimatedColor from '../nodes/AnimatedColor';
24+
2325
export type TimingAnimationConfig = {
2426
...AnimationConfig,
2527
toValue:
@@ -31,6 +33,14 @@ export type TimingAnimationConfig = {
3133
...
3234
}
3335
| AnimatedValueXY
36+
| {
37+
r: number,
38+
g: number,
39+
b: number,
40+
a: number,
41+
...
42+
}
43+
| AnimatedColor
3444
| AnimatedInterpolation,
3545
easing?: (value: number) => number,
3646
duration?: number,

0 commit comments

Comments
 (0)