Skip to content

Commit d3a0c41

Browse files
genkikondofacebook-github-bot
authored andcommitted
Support string color values in Animated.Color
Summary: In addition to rgba values, allow creating Animated.Color with a string color. Followup changes will include support for platform colors and native driver. Changelog: [General][Added] - Support string color values in Animated.Color Reviewed By: javache Differential Revision: D33810717 fbshipit-source-id: 208bc2675b6153a515fbf2122da15a065c473e73
1 parent 34dcbfb commit d3a0c41

File tree

2 files changed

+125
-34
lines changed

2 files changed

+125
-34
lines changed

Libraries/Animated/__tests__/Animated-test.js

+31
Original file line numberDiff line numberDiff line change
@@ -974,6 +974,37 @@ describe('Animated tests', () => {
974974
});
975975

976976
describe('Animated Colors', () => {
977+
it('should normalize colors', () => {
978+
let color = new Animated.Color();
979+
expect(color.__getValue()).toEqual('rgba(0, 0, 0, 1)');
980+
981+
color = new Animated.Color({r: 11, g: 22, b: 33, a: 1.0});
982+
expect(color.__getValue()).toEqual('rgba(11, 22, 33, 1)');
983+
984+
color = new Animated.Color('rgba(255, 0, 0, 1.0)');
985+
expect(color.__getValue()).toEqual('rgba(255, 0, 0, 1)');
986+
987+
color = new Animated.Color('#ff0000ff');
988+
expect(color.__getValue()).toEqual('rgba(255, 0, 0, 1)');
989+
990+
color = new Animated.Color('red');
991+
expect(color.__getValue()).toEqual('rgba(255, 0, 0, 1)');
992+
993+
color = new Animated.Color({
994+
r: new Animated.Value(255),
995+
g: new Animated.Value(0),
996+
b: new Animated.Value(0),
997+
a: new Animated.Value(1.0),
998+
});
999+
expect(color.__getValue()).toEqual('rgba(255, 0, 0, 1)');
1000+
1001+
color = new Animated.Color('unknown');
1002+
expect(color.__getValue()).toEqual('rgba(0, 0, 0, 1)');
1003+
1004+
color = new Animated.Color({key: 'value'});
1005+
expect(color.__getValue()).toEqual('rgba(0, 0, 0, 1)');
1006+
});
1007+
9771008
it('should animate colors', () => {
9781009
const color = new Animated.Color({r: 255, g: 0, b: 0, a: 1.0});
9791010
const callback = jest.fn();

Libraries/Animated/nodes/AnimatedColor.js

+94-34
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,79 @@
1212

1313
import AnimatedValue from './AnimatedValue';
1414
import AnimatedWithChildren from './AnimatedWithChildren';
15-
import invariant from 'invariant';
15+
import normalizeColor from '../../StyleSheet/normalizeColor';
16+
import {processColorObject} from '../../StyleSheet/PlatformColorValueTypes';
17+
18+
import type {ColorValue} from '../../StyleSheet/StyleSheet';
19+
import type {NativeColorValue} from '../../StyleSheet/PlatformColorValueTypes';
1620

1721
type ColorListenerCallback = (value: string) => mixed;
22+
type RgbaValue = {
23+
+r: number,
24+
+g: number,
25+
+b: number,
26+
+a: number,
27+
...
28+
};
29+
type RgbaAnimatedValue = {
30+
+r: AnimatedValue,
31+
+g: AnimatedValue,
32+
+b: AnimatedValue,
33+
+a: AnimatedValue,
34+
...
35+
};
1836

37+
const defaultColor: RgbaValue = {r: 0, g: 0, b: 0, a: 1.0};
1938
let _uniqueId = 1;
2039

40+
/* eslint no-bitwise: 0 */
41+
function processColor(color?: ?ColorValue): ?(RgbaValue | NativeColorValue) {
42+
if (color === undefined || color === null) {
43+
return null;
44+
}
45+
46+
let normalizedColor = normalizeColor(color);
47+
if (normalizedColor === undefined || normalizedColor === null) {
48+
return null;
49+
}
50+
51+
if (typeof normalizedColor === 'object') {
52+
const processedColorObj = processColorObject(normalizedColor);
53+
if (processedColorObj != null) {
54+
return processedColorObj;
55+
}
56+
} else if (typeof normalizedColor === 'number') {
57+
const r = (normalizedColor & 0xff000000) >>> 24;
58+
const g = (normalizedColor & 0x00ff0000) >>> 16;
59+
const b = (normalizedColor & 0x0000ff00) >>> 8;
60+
const a = (normalizedColor & 0x000000ff) / 255;
61+
62+
return {r, g, b, a};
63+
}
64+
65+
return null;
66+
}
67+
68+
function isRgbaValue(value: any): boolean {
69+
return (
70+
value &&
71+
typeof value.r === 'number' &&
72+
typeof value.g === 'number' &&
73+
typeof value.b === 'number' &&
74+
typeof value.a === 'number'
75+
);
76+
}
77+
78+
function isRgbaAnimatedValue(value: any): boolean {
79+
return (
80+
value &&
81+
value.r instanceof AnimatedValue &&
82+
value.g instanceof AnimatedValue &&
83+
value.b instanceof AnimatedValue &&
84+
value.a instanceof AnimatedValue
85+
);
86+
}
87+
2188
export default class AnimatedColor extends AnimatedWithChildren {
2289
r: AnimatedValue;
2390
g: AnimatedValue;
@@ -34,39 +101,32 @@ export default class AnimatedColor extends AnimatedWithChildren {
34101
...
35102
};
36103

37-
constructor(
38-
valueIn?: ?{
39-
+r: number | AnimatedValue,
40-
+g: number | AnimatedValue,
41-
+b: number | AnimatedValue,
42-
+a: number | AnimatedValue,
43-
...
44-
}, // TODO: support string color and platform color
45-
) {
104+
constructor(valueIn?: ?(RgbaValue | RgbaAnimatedValue | ColorValue)) {
46105
super();
47-
const value: any = valueIn || {r: 0, g: 0, b: 0, a: 1}; // @flowfixme: shouldn't need `: any`
48-
if (
49-
typeof value.r === 'number' &&
50-
typeof value.g === 'number' &&
51-
typeof value.b === 'number' &&
52-
typeof value.a === 'number'
53-
) {
54-
this.r = new AnimatedValue(value.r);
55-
this.g = new AnimatedValue(value.g);
56-
this.b = new AnimatedValue(value.b);
57-
this.a = new AnimatedValue(value.a);
106+
let value: RgbaValue | RgbaAnimatedValue | ColorValue =
107+
valueIn || defaultColor;
108+
109+
if (isRgbaAnimatedValue(value)) {
110+
// $FlowIgnore[incompatible-cast] - Type is verified above
111+
const rgbaAnimatedValue: RgbaAnimatedValue = (value: RgbaAnimatedValue);
112+
this.r = rgbaAnimatedValue.r;
113+
this.g = rgbaAnimatedValue.g;
114+
this.b = rgbaAnimatedValue.b;
115+
this.a = rgbaAnimatedValue.a;
58116
} else {
59-
invariant(
60-
value.r instanceof AnimatedValue &&
61-
value.g instanceof AnimatedValue &&
62-
value.b instanceof AnimatedValue &&
63-
value.a instanceof AnimatedValue,
64-
'AnimatedColor must be initialized with an object of numbers or AnimatedValues.',
65-
);
66-
this.r = value.r;
67-
this.g = value.g;
68-
this.b = value.b;
69-
this.a = value.a;
117+
// Handle potential parsable string color or platform color object
118+
if (!isRgbaValue(value)) {
119+
// $FlowIgnore[incompatible-cast] - Type is verified via conditionals
120+
value = processColor((value: ColorValue)) || {r: 0, g: 0, b: 0, a: 1.0};
121+
// TODO: support platform color
122+
}
123+
124+
// $FlowIgnore[incompatible-cast] - Type is verified via conditionals
125+
const rgbaValue: RgbaValue = (value: RgbaValue);
126+
this.r = new AnimatedValue(rgbaValue.r);
127+
this.g = new AnimatedValue(rgbaValue.g);
128+
this.b = new AnimatedValue(rgbaValue.b);
129+
this.a = new AnimatedValue(rgbaValue.a);
70130
}
71131
this._listeners = {};
72132
}
@@ -75,7 +135,7 @@ export default class AnimatedColor extends AnimatedWithChildren {
75135
* Directly set the value. This will stop any animations running on the value
76136
* and update all the bound properties.
77137
*/
78-
setValue(value: {r: number, g: number, b: number, a: number, ...}) {
138+
setValue(value: {r: number, g: number, b: number, a: number, ...}): void {
79139
this.r.setValue(value.r);
80140
this.g.setValue(value.g);
81141
this.b.setValue(value.b);
@@ -87,7 +147,7 @@ export default class AnimatedColor extends AnimatedWithChildren {
87147
* via `setValue`, an animation, or `Animated.event`. Useful for compensating
88148
* things like the start of a pan gesture.
89149
*/
90-
setOffset(offset: {r: number, g: number, b: number, a: number, ...}) {
150+
setOffset(offset: {r: number, g: number, b: number, a: number, ...}): void {
91151
this.r.setOffset(offset.r);
92152
this.g.setOffset(offset.g);
93153
this.b.setOffset(offset.b);

0 commit comments

Comments
 (0)