From ec6d4fb0019b150674fa406bd98f2b3c741909df Mon Sep 17 00:00:00 2001 From: Genki Kondo Date: Fri, 31 Mar 2023 09:03:58 -0700 Subject: [PATCH] Modify AnimatedProps and AnimatedStyle to use AnimatedObject Summary: AnimatedObject is a more generic version of AnimatedTransform, able to handle animated values within arrays and objects. This is useful for props of native components that may need to be animated per field. This diff hooks up AnimatedObject to AnimatedProps and AnimatedStyle for values that are arrays or objects. Changelog: [Internal][Added] - Modify AnimatedProps and AnimatedStyle to use AnimatedObject Reviewed By: rshest Differential Revision: D44315336 fbshipit-source-id: 34ab129cae256681440ae5ca412519e506269906 --- .../Animated/__tests__/Animated-test.js | 3 +- .../Animated/__tests__/Animated-web-test.js | 3 +- .../Libraries/Animated/nodes/AnimatedProps.js | 26 +++++-- .../Libraries/Animated/nodes/AnimatedStyle.js | 78 ++++++------------- 4 files changed, 47 insertions(+), 63 deletions(-) diff --git a/packages/react-native/Libraries/Animated/__tests__/Animated-test.js b/packages/react-native/Libraries/Animated/__tests__/Animated-test.js index eca451af1ba4dc..7796168b19502c 100644 --- a/packages/react-native/Libraries/Animated/__tests__/Animated-test.js +++ b/packages/react-native/Libraries/Animated/__tests__/Animated-test.js @@ -78,7 +78,8 @@ describe('Animated tests', () => { node.__attach(); - expect(anim.__getChildren().length).toBe(3); + // Children: [AnimatedStyle, AnimatedTransform, AnimatedInterpolation, AnimatedObject, AnimatedObject] + expect(anim.__getChildren().length).toBe(5); anim.setValue(0.5); diff --git a/packages/react-native/Libraries/Animated/__tests__/Animated-web-test.js b/packages/react-native/Libraries/Animated/__tests__/Animated-web-test.js index 2fccade525c107..e393454b9e43bb 100644 --- a/packages/react-native/Libraries/Animated/__tests__/Animated-web-test.js +++ b/packages/react-native/Libraries/Animated/__tests__/Animated-web-test.js @@ -84,7 +84,8 @@ describe('Animated tests', () => { node.__attach(); - expect(anim.__getChildren().length).toBe(3); + // Children: [AnimatedStyle, AnimatedTransform, AnimatedInterpolation, AnimatedObject, AnimatedObject] + expect(anim.__getChildren().length).toBe(5); anim.setValue(0.5); diff --git a/packages/react-native/Libraries/Animated/nodes/AnimatedProps.js b/packages/react-native/Libraries/Animated/nodes/AnimatedProps.js index d6ebbb02e3bfc5..333f147ab68cae 100644 --- a/packages/react-native/Libraries/Animated/nodes/AnimatedProps.js +++ b/packages/react-native/Libraries/Animated/nodes/AnimatedProps.js @@ -16,9 +16,27 @@ import {findNodeHandle} from '../../ReactNative/RendererProxy'; import {AnimatedEvent} from '../AnimatedEvent'; import NativeAnimatedHelper from '../NativeAnimatedHelper'; import AnimatedNode from './AnimatedNode'; +import AnimatedObject, {hasAnimatedNode} from './AnimatedObject'; import AnimatedStyle from './AnimatedStyle'; import invariant from 'invariant'; +function createAnimatedProps(inputProps: Object): Object { + const props: Object = {}; + for (const key in inputProps) { + const value = inputProps[key]; + if (key === 'style') { + props[key] = new AnimatedStyle(value); + } else if (value instanceof AnimatedNode) { + props[key] = value; + } else if (key !== 'children' && hasAnimatedNode(value)) { + props[key] = new AnimatedObject(value); + } else { + props[key] = value; + } + } + return props; +} + export default class AnimatedProps extends AnimatedNode { _props: Object; _animatedView: any; @@ -26,13 +44,7 @@ export default class AnimatedProps extends AnimatedNode { constructor(props: Object, callback: () => void) { super(); - if (props.style) { - props = { - ...props, - style: new AnimatedStyle(props.style), - }; - } - this._props = props; + this._props = createAnimatedProps(props); this._callback = callback; } diff --git a/packages/react-native/Libraries/Animated/nodes/AnimatedStyle.js b/packages/react-native/Libraries/Animated/nodes/AnimatedStyle.js index e851b6031de9bb..86a100f485e32e 100644 --- a/packages/react-native/Libraries/Animated/nodes/AnimatedStyle.js +++ b/packages/react-native/Libraries/Animated/nodes/AnimatedStyle.js @@ -16,10 +16,14 @@ import flattenStyle from '../../StyleSheet/flattenStyle'; import Platform from '../../Utilities/Platform'; import NativeAnimatedHelper from '../NativeAnimatedHelper'; import AnimatedNode from './AnimatedNode'; +import AnimatedObject, {hasAnimatedNode} from './AnimatedObject'; import AnimatedTransform from './AnimatedTransform'; import AnimatedWithChildren from './AnimatedWithChildren'; -function createAnimatedStyle(inputStyle: any): Object { +function createAnimatedStyle( + inputStyle: any, + keepUnanimatedValues: boolean, +): Object { // $FlowFixMe[underconstrained-implicit-instantiation] const style = flattenStyle(inputStyle); const animatedStyles: any = {}; @@ -29,82 +33,48 @@ function createAnimatedStyle(inputStyle: any): Object { animatedStyles[key] = new AnimatedTransform(value); } else if (value instanceof AnimatedNode) { animatedStyles[key] = value; - } else if (value && !Array.isArray(value) && typeof value === 'object') { - animatedStyles[key] = createAnimatedStyle(value); + } else if (hasAnimatedNode(value)) { + animatedStyles[key] = new AnimatedObject(value); + } else if (keepUnanimatedValues) { + animatedStyles[key] = value; } } return animatedStyles; } -function createStyleWithAnimatedTransform(inputStyle: any): Object { - // $FlowFixMe[underconstrained-implicit-instantiation] - let style = flattenStyle(inputStyle) || ({}: {[string]: any}); - - if (style.transform) { - style = { - ...style, - transform: new AnimatedTransform(style.transform), - }; - } - return style; -} - export default class AnimatedStyle extends AnimatedWithChildren { _inputStyle: any; _style: Object; constructor(style: any) { super(); - if (Platform.OS === 'web') { - this._inputStyle = style; - this._style = createAnimatedStyle(style); - } else { - this._style = createStyleWithAnimatedTransform(style); - } + this._inputStyle = style; + this._style = createAnimatedStyle(style, Platform.OS !== 'web'); } - // Recursively get values for nested styles (like iOS's shadowOffset) - _walkStyleAndGetValues(style: any): {[string]: any | {...}} { - const updatedStyle: {[string]: any | {...}} = {}; - for (const key in style) { - const value = style[key]; + __getValue(): Object | Array { + const result: {[string]: any} = {}; + for (const key in this._style) { + const value = this._style[key]; if (value instanceof AnimatedNode) { - updatedStyle[key] = value.__getValue(); - } else if (value && !Array.isArray(value) && typeof value === 'object') { - // Support animating nested values (for example: shadowOffset.height) - updatedStyle[key] = this._walkStyleAndGetValues(value); + result[key] = value.__getValue(); } else { - updatedStyle[key] = value; + result[key] = value; } } - return updatedStyle; - } - __getValue(): Object | Array { - if (Platform.OS === 'web') { - return [this._inputStyle, this._walkStyleAndGetValues(this._style)]; - } - - return this._walkStyleAndGetValues(this._style); + return Platform.OS === 'web' ? [this._inputStyle, result] : result; } - // Recursively get animated values for nested styles (like iOS's shadowOffset) - _walkStyleAndGetAnimatedValues(style: any): {[string]: any | {...}} { - const updatedStyle: {[string]: any | {...}} = {}; - for (const key in style) { - const value = style[key]; + __getAnimatedValue(): Object { + const result: {[string]: any} = {}; + for (const key in this._style) { + const value = this._style[key]; if (value instanceof AnimatedNode) { - updatedStyle[key] = value.__getAnimatedValue(); - } else if (value && !Array.isArray(value) && typeof value === 'object') { - // Support animating nested values (for example: shadowOffset.height) - updatedStyle[key] = this._walkStyleAndGetAnimatedValues(value); + result[key] = value.__getAnimatedValue(); } } - return updatedStyle; - } - - __getAnimatedValue(): Object { - return this._walkStyleAndGetAnimatedValues(this._style); + return result; } __attach(): void {