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

Update the Animated API #1451

Closed
wants to merge 10 commits into from
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @flow strict-local
* @format
*/
'use strict';
Expand All @@ -13,7 +13,7 @@ import invariant from 'fbjs/lib/invariant';
import NativeModules from '../../../exports/NativeModules';
import NativeEventEmitter from '../NativeEventEmitter';

import type {AnimationConfig} from './animations/Animation';
import type {AnimationConfig, EndCallback} from './animations/Animation';
import type {EventConfig} from './AnimatedEvent';

const NativeAnimatedModule = NativeModules.NativeAnimatedModule;
Expand All @@ -22,20 +22,44 @@ let __nativeAnimatedNodeTagCount = 1; /* used for animated nodes */
let __nativeAnimationIdCount = 1; /* used for started animations */

type EndResult = {finished: boolean};
type EndCallback = (result: EndResult) => void;
type EventMapping = {
nativeEventPath: Array<string>,
animatedValueTag: ?number,
};
type AnimatingNodeConfig = {|
// TODO: Type this with better enums.
type: string,
|};
type AnimatedNodeConfig = {|
// TODO: Type this with better enums.
type: string,
|};

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

let nativeEventEmitter;

let queueConnections = false;
const queue = [];

/**
* Simple wrappers around NativeAnimatedModule to provide flow and autocmplete support for
* Simple wrappers around NativeAnimatedModule to provide flow and autocomplete support for
* the native module methods
*/
const API = {
createAnimatedNode: function(tag: ?number, config: Object): void {
enableQueue: function(): void {
queueConnections = true;
},
disableQueue: function(): void {
assertNativeAnimatedModule();
queueConnections = false;
for (let q = 0, l = queue.length; q < l; q++) {
const args = queue[q];
NativeAnimatedModule.connectAnimatedNodes(args[0], args[1]);
}
queue.length = 0;
},
createAnimatedNode: function(tag: ?number, config: AnimatedNodeConfig): void {
assertNativeAnimatedModule();
NativeAnimatedModule.createAnimatedNode(tag, config);
},
Expand All @@ -49,6 +73,10 @@ const API = {
},
connectAnimatedNodes: function(parentTag: ?number, childTag: ?number): void {
assertNativeAnimatedModule();
if (queueConnections) {
queue.push([parentTag, childTag]);
return;
}
NativeAnimatedModule.connectAnimatedNodes(parentTag, childTag);
},
disconnectAnimatedNodes: function(
Expand All @@ -61,7 +89,7 @@ const API = {
startAnimatingNode: function(
animationId: ?number,
nodeTag: ?number,
config: Object,
config: AnimatingNodeConfig,
endCallback: EndCallback,
): void {
assertNativeAnimatedModule();
Expand Down Expand Up @@ -145,6 +173,16 @@ const API = {
const STYLES_WHITELIST = {
opacity: true,
transform: true,
borderRadius: true,
borderBottomEndRadius: true,
borderBottomLeftRadius: true,
borderBottomRightRadius: true,
borderBottomStartRadius: true,
borderTopEndRadius: true,
borderTopLeftRadius: true,
borderTopRightRadius: true,
borderTopStartRadius: true,
elevation: true,
/* ios styles */
shadowOpacity: true,
shadowRadius: true,
Expand Down Expand Up @@ -187,7 +225,12 @@ function addWhitelistedInterpolationParam(param: string): void {
SUPPORTED_INTERPOLATION_PARAMS[param] = true;
}

function validateTransform(configs: Array<Object>): void {
function validateTransform(
configs: Array<
| {type: 'animated', property: string, nodeTag: ?number}
| {type: 'static', property: string, value: number | string},
>,
): void {
configs.forEach(config => {
if (!TRANSFORM_WHITELIST.hasOwnProperty(config.property)) {
throw new Error(
Expand All @@ -199,8 +242,8 @@ function validateTransform(configs: Array<Object>): void {
});
}

function validateStyles(styles: Object): void {
for (var key in styles) {
function validateStyles(styles: {[key: string]: ?number}): void {
for (const key in styles) {
if (!STYLES_WHITELIST.hasOwnProperty(key)) {
throw new Error(
`Style property '${key}' is not supported by native animated module`,
Expand All @@ -209,8 +252,8 @@ function validateStyles(styles: Object): void {
}
}

function validateInterpolation(config: Object): void {
for (var key in config) {
function validateInterpolation(config: InterpolationConfigType): void {
for (const key in config) {
if (!SUPPORTED_INTERPOLATION_PARAMS.hasOwnProperty(key)) {
throw new Error(
`Interpolation property '${key}' is not supported by native animated module`,
Expand All @@ -234,7 +277,7 @@ function assertNativeAnimatedModule(): void {
let _warnedMissingNativeAnimated = false;

function shouldUseNativeDriver(config: AnimationConfig | EventConfig): boolean {
if (config.useNativeDriver && !NativeAnimatedModule) {
if (config.useNativeDriver === true && !NativeAnimatedModule) {
if (!_warnedMissingNativeAnimated) {
console.warn(
'Animated: `useNativeDriver` is not supported because the native ' +
Expand All @@ -251,6 +294,21 @@ function shouldUseNativeDriver(config: AnimationConfig | EventConfig): boolean {
return config.useNativeDriver || false;
}

function transformDataType(value: number | string): number | string {
// Change the string type to number type so we can reuse the same logic in
// iOS and Android platform
if (typeof value !== 'string') {
return value;
}
if (/deg$/.test(value)) {
const degrees = parseFloat(value) || 0;
const radians = (degrees * Math.PI) / 180.0;
return radians;
} else {
return value;
}
}

const NativeAnimatedHelper = {
API,
addWhitelistedStyleProp,
Expand All @@ -263,6 +321,7 @@ const NativeAnimatedHelper = {
generateNewAnimationId,
assertNativeAnimatedModule,
shouldUseNativeDriver,
transformDataType,
get nativeEventEmitter() {
if (!nativeEventEmitter) {
nativeEventEmitter = new NativeEventEmitter(NativeAnimatedModule);
Expand All @@ -282,7 +341,8 @@ export {
generateNewNodeTag,
generateNewAnimationId,
assertNativeAnimatedModule,
shouldUseNativeDriver
shouldUseNativeDriver,
transformDataType,
};

export default NativeAnimatedHelper;
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ class Animation {
onEnd && onEnd(result);
}
__startNativeAnimation(animatedValue: AnimatedValue): void {
NativeAnimatedHelper.API.enableQueue();
animatedValue.__makeNative();
NativeAnimatedHelper.API.disableQueue();
this.__nativeId = NativeAnimatedHelper.generateNewAnimationId();
NativeAnimatedHelper.API.startAnimatingNode(
this.__nativeId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class DecayAnimation extends Animation {
this._velocity = config.velocity;
this._useNativeDriver = shouldUseNativeDriver(config);
this.__isInteraction =
config.isInteraction !== undefined ? config.isInteraction : true;
config.isInteraction !== undefined ? config.isInteraction : !this._useNativeDriver;
this.__iterations = config.iterations !== undefined ? config.iterations : 1;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class SpringAnimation extends Animation {
this._delay = withDefault(config.delay, 0);
this._useNativeDriver = shouldUseNativeDriver(config);
this.__isInteraction =
config.isInteraction !== undefined ? config.isInteraction : true;
config.isInteraction !== undefined ? config.isInteraction : !this._useNativeDriver;
this.__iterations = config.iterations !== undefined ? config.iterations : 1;

if (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import React from 'react';
import ViewStylePropTypes from '../../../exports/View/ViewStylePropTypes';
import invariant from 'fbjs/lib/invariant';

function createAnimatedComponent(Component: any): any {
function createAnimatedComponent(Component: any, defaultProps: any): any {
invariant(
typeof Component === 'string' ||
(Component.prototype && Component.prototype.isReactComponent),
Expand All @@ -29,13 +29,11 @@ function createAnimatedComponent(Component: any): any {
_prevComponent: any;
_propsAnimated: AnimatedProps;
_eventDetachers: Array<Function> = [];
_setComponentRef: Function;

static __skipSetNativeProps_FOR_TESTS_ONLY = false;

constructor(props: Object) {
super(props);
this._setComponentRef = this._setComponentRef.bind(this);
}

componentWillUnmount() {
Expand Down Expand Up @@ -150,6 +148,7 @@ function createAnimatedComponent(Component: any): any {
const props = this._propsAnimated.__getValue();
return (
<Component
{...defaultProps}
{...props}
ref={this._setComponentRef}
// The native driver updates views directly through the UI thread so we
Expand All @@ -163,7 +162,7 @@ function createAnimatedComponent(Component: any): any {
);
}

_setComponentRef(c) {
_setComponentRef = c => {
this._prevComponent = this._component;
this._component = c;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ function colorToRgba(input: string): string {
return `rgba(${r}, ${g}, ${b}, ${a})`;
}

const stringShapeRegex = /[0-9\.-]+/g;
const stringShapeRegex = /[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/g;

/**
* Supports string shapes by extracting numbers so new values can be computed,
Expand Down Expand Up @@ -242,10 +242,11 @@ function createInterpolationFromStringOutputRange(
// ->
// 'rgba(${interpolations[0](input)}, ${interpolations[1](input)}, ...'
return outputRange[0].replace(stringShapeRegex, () => {
const val = +interpolations[i++](input);
const rounded =
shouldRound && i < 4 ? Math.round(val) : Math.round(val * 1000) / 1000;
return String(rounded);
let val = +interpolations[i++](input);
if (shouldRound) {
val = i < 4 ? Math.round(val) : Math.round(val * 1000) / 1000;
}
return String(val);
});
};
}
Expand Down Expand Up @@ -285,7 +286,7 @@ function checkValidInputRange(arr: Array<number>) {
* mean this implicit string conversion, you can do something like
* String(myThing)
*/
'inputRange must be monotonically increasing ' + arr,
'inputRange must be monotonically non-decreasing ' + arr,
);
}
}
Expand Down Expand Up @@ -346,22 +347,8 @@ class AnimatedInterpolation extends AnimatedWithChildren {
super.__detach();
}

__transformDataType(range: Array<string>): Array<number> {
// Change the string array type to number array
// So we can reuse the same logic in iOS and Android platform
return range.map(function(value) {
if (typeof value !== 'string') {
return value;
}
if (/deg$/.test(value)) {
const degrees = parseFloat(value) || 0;
const radians = degrees * Math.PI / 180.0;
return radians;
} else {
// Assume radians
return parseFloat(value) || 0;
}
});
__transformDataType(range: Array<any>) {
return range.map(NativeAnimatedHelper.transformDataType);
}

__getNativeConfig(): any {
Expand Down
Loading