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
This commit is contained in:
Genki Kondo 2022-01-27 17:34:37 -08:00 коммит произвёл Facebook GitHub Bot
Родитель 34dcbfb8d9
Коммит d3a0c4129d
2 изменённых файлов: 125 добавлений и 34 удалений

Просмотреть файл

@ -974,6 +974,37 @@ describe('Animated tests', () => {
}); });
describe('Animated Colors', () => { describe('Animated Colors', () => {
it('should normalize colors', () => {
let color = new Animated.Color();
expect(color.__getValue()).toEqual('rgba(0, 0, 0, 1)');
color = new Animated.Color({r: 11, g: 22, b: 33, a: 1.0});
expect(color.__getValue()).toEqual('rgba(11, 22, 33, 1)');
color = new Animated.Color('rgba(255, 0, 0, 1.0)');
expect(color.__getValue()).toEqual('rgba(255, 0, 0, 1)');
color = new Animated.Color('#ff0000ff');
expect(color.__getValue()).toEqual('rgba(255, 0, 0, 1)');
color = new Animated.Color('red');
expect(color.__getValue()).toEqual('rgba(255, 0, 0, 1)');
color = new Animated.Color({
r: new Animated.Value(255),
g: new Animated.Value(0),
b: new Animated.Value(0),
a: new Animated.Value(1.0),
});
expect(color.__getValue()).toEqual('rgba(255, 0, 0, 1)');
color = new Animated.Color('unknown');
expect(color.__getValue()).toEqual('rgba(0, 0, 0, 1)');
color = new Animated.Color({key: 'value'});
expect(color.__getValue()).toEqual('rgba(0, 0, 0, 1)');
});
it('should animate colors', () => { it('should animate colors', () => {
const color = new Animated.Color({r: 255, g: 0, b: 0, a: 1.0}); const color = new Animated.Color({r: 255, g: 0, b: 0, a: 1.0});
const callback = jest.fn(); const callback = jest.fn();

Просмотреть файл

@ -12,12 +12,79 @@
import AnimatedValue from './AnimatedValue'; import AnimatedValue from './AnimatedValue';
import AnimatedWithChildren from './AnimatedWithChildren'; import AnimatedWithChildren from './AnimatedWithChildren';
import invariant from 'invariant'; import normalizeColor from '../../StyleSheet/normalizeColor';
import {processColorObject} from '../../StyleSheet/PlatformColorValueTypes';
import type {ColorValue} from '../../StyleSheet/StyleSheet';
import type {NativeColorValue} from '../../StyleSheet/PlatformColorValueTypes';
type ColorListenerCallback = (value: string) => mixed; type ColorListenerCallback = (value: string) => mixed;
type RgbaValue = {
+r: number,
+g: number,
+b: number,
+a: number,
...
};
type RgbaAnimatedValue = {
+r: AnimatedValue,
+g: AnimatedValue,
+b: AnimatedValue,
+a: AnimatedValue,
...
};
const defaultColor: RgbaValue = {r: 0, g: 0, b: 0, a: 1.0};
let _uniqueId = 1; let _uniqueId = 1;
/* eslint no-bitwise: 0 */
function processColor(color?: ?ColorValue): ?(RgbaValue | NativeColorValue) {
if (color === undefined || color === null) {
return null;
}
let normalizedColor = normalizeColor(color);
if (normalizedColor === undefined || normalizedColor === null) {
return null;
}
if (typeof normalizedColor === 'object') {
const processedColorObj = processColorObject(normalizedColor);
if (processedColorObj != null) {
return processedColorObj;
}
} else if (typeof normalizedColor === 'number') {
const r = (normalizedColor & 0xff000000) >>> 24;
const g = (normalizedColor & 0x00ff0000) >>> 16;
const b = (normalizedColor & 0x0000ff00) >>> 8;
const a = (normalizedColor & 0x000000ff) / 255;
return {r, g, b, a};
}
return null;
}
function isRgbaValue(value: any): boolean {
return (
value &&
typeof value.r === 'number' &&
typeof value.g === 'number' &&
typeof value.b === 'number' &&
typeof value.a === 'number'
);
}
function isRgbaAnimatedValue(value: any): boolean {
return (
value &&
value.r instanceof AnimatedValue &&
value.g instanceof AnimatedValue &&
value.b instanceof AnimatedValue &&
value.a instanceof AnimatedValue
);
}
export default class AnimatedColor extends AnimatedWithChildren { export default class AnimatedColor extends AnimatedWithChildren {
r: AnimatedValue; r: AnimatedValue;
g: AnimatedValue; g: AnimatedValue;
@ -34,39 +101,32 @@ export default class AnimatedColor extends AnimatedWithChildren {
... ...
}; };
constructor( constructor(valueIn?: ?(RgbaValue | RgbaAnimatedValue | ColorValue)) {
valueIn?: ?{
+r: number | AnimatedValue,
+g: number | AnimatedValue,
+b: number | AnimatedValue,
+a: number | AnimatedValue,
...
}, // TODO: support string color and platform color
) {
super(); super();
const value: any = valueIn || {r: 0, g: 0, b: 0, a: 1}; // @flowfixme: shouldn't need `: any` let value: RgbaValue | RgbaAnimatedValue | ColorValue =
if ( valueIn || defaultColor;
typeof value.r === 'number' &&
typeof value.g === 'number' && if (isRgbaAnimatedValue(value)) {
typeof value.b === 'number' && // $FlowIgnore[incompatible-cast] - Type is verified above
typeof value.a === 'number' const rgbaAnimatedValue: RgbaAnimatedValue = (value: RgbaAnimatedValue);
) { this.r = rgbaAnimatedValue.r;
this.r = new AnimatedValue(value.r); this.g = rgbaAnimatedValue.g;
this.g = new AnimatedValue(value.g); this.b = rgbaAnimatedValue.b;
this.b = new AnimatedValue(value.b); this.a = rgbaAnimatedValue.a;
this.a = new AnimatedValue(value.a);
} else { } else {
invariant( // Handle potential parsable string color or platform color object
value.r instanceof AnimatedValue && if (!isRgbaValue(value)) {
value.g instanceof AnimatedValue && // $FlowIgnore[incompatible-cast] - Type is verified via conditionals
value.b instanceof AnimatedValue && value = processColor((value: ColorValue)) || {r: 0, g: 0, b: 0, a: 1.0};
value.a instanceof AnimatedValue, // TODO: support platform color
'AnimatedColor must be initialized with an object of numbers or AnimatedValues.', }
);
this.r = value.r; // $FlowIgnore[incompatible-cast] - Type is verified via conditionals
this.g = value.g; const rgbaValue: RgbaValue = (value: RgbaValue);
this.b = value.b; this.r = new AnimatedValue(rgbaValue.r);
this.a = value.a; this.g = new AnimatedValue(rgbaValue.g);
this.b = new AnimatedValue(rgbaValue.b);
this.a = new AnimatedValue(rgbaValue.a);
} }
this._listeners = {}; this._listeners = {};
} }
@ -75,7 +135,7 @@ export default class AnimatedColor extends AnimatedWithChildren {
* Directly set the value. This will stop any animations running on the value * Directly set the value. This will stop any animations running on the value
* and update all the bound properties. * and update all the bound properties.
*/ */
setValue(value: {r: number, g: number, b: number, a: number, ...}) { setValue(value: {r: number, g: number, b: number, a: number, ...}): void {
this.r.setValue(value.r); this.r.setValue(value.r);
this.g.setValue(value.g); this.g.setValue(value.g);
this.b.setValue(value.b); this.b.setValue(value.b);
@ -87,7 +147,7 @@ export default class AnimatedColor extends AnimatedWithChildren {
* via `setValue`, an animation, or `Animated.event`. Useful for compensating * via `setValue`, an animation, or `Animated.event`. Useful for compensating
* things like the start of a pan gesture. * things like the start of a pan gesture.
*/ */
setOffset(offset: {r: number, g: number, b: number, a: number, ...}) { setOffset(offset: {r: number, g: number, b: number, a: number, ...}): void {
this.r.setOffset(offset.r); this.r.setOffset(offset.r);
this.g.setOffset(offset.g); this.g.setOffset(offset.g);
this.b.setOffset(offset.b); this.b.setOffset(offset.b);