From 9395a598683a0aa5cfd29412e3b07ad645f2dad5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20B=C5=82oniarz?= Date: Mon, 13 Nov 2023 16:38:53 +0100 Subject: [PATCH 01/58] Update layout animations --- Example/ios/Podfile.lock | 4 +- .../LayoutAnimations/WaterfallGridExample.tsx | 8 +- .../createAnimatedComponent.tsx | 81 +++++++++++-------- src/reanimated2/component/FlatList.tsx | 15 ++-- 4 files changed, 64 insertions(+), 44 deletions(-) diff --git a/Example/ios/Podfile.lock b/Example/ios/Podfile.lock index de6333de774..9b740c78fb8 100644 --- a/Example/ios/Podfile.lock +++ b/Example/ios/Podfile.lock @@ -798,7 +798,7 @@ SPEC CHECKSUMS: RNCMaskedView: f7c74478c83c4fdfc5cf4df51f80c0dd5cf125c6 RNCPicker: 529d564911e93598cc399b56cc0769ce3675f8c8 RNGestureHandler: bb86e378287f7713baf3ca205423eb8109790022 - RNReanimated: 7032c33fc6caf259b4872375342b72183c688fc3 + RNReanimated: 31c2181e76a33a8f04ae0420b00540c3e27861b7 RNScreens: 85d3880b52d34db7b8eeebe2f1a0e807c05e69fa RNSVG: d00c8f91c3cbf6d476451313a18f04d220d4f396 SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 @@ -807,4 +807,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: f37cea0ea1b6dc523fb88a9c2bdb9993f4b567ce -COCOAPODS: 1.14.0 +COCOAPODS: 1.14.2 diff --git a/app/src/examples/LayoutAnimations/WaterfallGridExample.tsx b/app/src/examples/LayoutAnimations/WaterfallGridExample.tsx index 6438fbb95a9..7c3a0c726a3 100644 --- a/app/src/examples/LayoutAnimations/WaterfallGridExample.tsx +++ b/app/src/examples/LayoutAnimations/WaterfallGridExample.tsx @@ -98,11 +98,14 @@ export function WaterfallGrid({ } setPoks(poks); }, [dims, setPoks, pokemons]); + const layoutTransition = useMemo( + () => getLayoutTranistion(transition), + [transition] + ); const [cardsMemo, height] = useMemo<[Array, number]>(() => { if (poks.length === 0) { return [[], 0]; } - const layoutTransition = getLayoutTranistion(transition); const cardsResult: Array = []; const heights = new Array(columns).fill(0); for (const pok of poks) { @@ -139,7 +142,7 @@ export function WaterfallGrid({ ); } return [cardsResult, Math.max(...heights) + margin / 2]; - }, [poks, columns, transition, width]); + }, [poks, columns, layoutTransition, width]); return ( {cardsMemo.length === 0 && Loading } @@ -179,7 +182,6 @@ export default function WaterfallGridExample() { { + 'worklet'; + return { initialValues: {}, animations: {} }; +}; function onlyAnimatedStyles(styles: StyleProps[]): StyleProps[] { return styles.filter((style) => style?.viewDescriptors); @@ -127,6 +131,11 @@ export function createAnimatedComponent( this._attachAnimatedStyles(); this._InlinePropManager.attachInlineProps(this, this._getViewInfo()); + const layout = this.props.layout; + if (layout) { + this._configureLayoutTransition(); + } + if (IS_WEB) { configureWebLayoutAnimations(); startWebLayoutAnimation( @@ -144,12 +153,30 @@ export function createAnimatedComponent( this._InlinePropManager.detachInlineProps(); this._sharedElementTransition?.unregisterTransition(this._viewTag); + const exiting = this.props.exiting; if (IS_WEB) { startWebLayoutAnimation( this.props, this._component as HTMLElement, LayoutAnimationType.EXITING ); + } else if (exiting) { + const reduceMotionInExiting = + 'getReduceMotion' in exiting && + typeof exiting.getReduceMotion === 'function' + ? getReduceMotionFromConfig(exiting.getReduceMotion()) + : getReduceMotionFromConfig(); + if (!reduceMotionInExiting) { + configureLayoutAnimations( + this._viewTag, + LayoutAnimationType.EXITING, + maybeBuild( + exiting, + this.props?.style, + AnimatedComponent.displayName + ) + ); + } } } @@ -395,6 +422,11 @@ export function createAnimatedComponent( // eslint-disable-next-line @typescript-eslint/no-explicit-any snapshot?: any ) { + const layout = this.props.layout; + const oldLayout = prevProps.layout; + if (layout !== oldLayout) { + this._configureLayoutTransition(); + } this._reattachNativeEvents(prevProps); this._attachAnimatedStyles(); this._InlinePropManager.attachInlineProps(this, this._getViewInfo()); @@ -409,6 +441,18 @@ export function createAnimatedComponent( } } + _configureLayoutTransition() { + configureLayoutAnimations( + this._viewTag, + LayoutAnimationType.LAYOUT, + maybeBuild( + this.props.layout ?? EMPTY_TRANSITION, + undefined /* We don't have to warn user if style has common properties with animation for LAYOUT */, + AnimatedComponent.displayName + ) + ); + } + _setComponentRef = setAndForwardRef({ getForwardedRef: () => this.props.forwardedRef as MutableRefObject< @@ -421,25 +465,12 @@ export function createAnimatedComponent( ? (ref as HTMLElement) : findNodeHandle(ref as Component); - const { layout, entering, exiting, sharedTransitionTag } = this.props; - if ( - (layout || entering || exiting || sharedTransitionTag) && - tag != null - ) { + const { entering, sharedTransitionTag } = this.props; + if ((entering || sharedTransitionTag) && tag != null) { if (!shouldBeUseWeb()) { enableLayoutAnimations(true, false); } - if (layout) { - configureLayoutAnimations( - tag, - LayoutAnimationType.LAYOUT, - maybeBuild( - layout, - undefined /* We don't have to warn user if style has common properties with animation for LAYOUT */, - AnimatedComponent.displayName - ) - ); - } + const skipEntering = this.context?.current; if (entering && !skipEntering) { configureLayoutAnimations( @@ -452,24 +483,6 @@ export function createAnimatedComponent( ) ); } - if (exiting) { - const reduceMotionInExiting = - 'getReduceMotion' in exiting && - typeof exiting.getReduceMotion === 'function' - ? getReduceMotionFromConfig(exiting.getReduceMotion()) - : getReduceMotionFromConfig(); - if (!reduceMotionInExiting) { - configureLayoutAnimations( - tag, - LayoutAnimationType.EXITING, - maybeBuild( - exiting, - this.props?.style, - AnimatedComponent.displayName - ) - ); - } - } if (sharedTransitionTag && !IS_WEB) { const sharedElementTransition = this.props.sharedTransitionStyle ?? new SharedTransition(); diff --git a/src/reanimated2/component/FlatList.tsx b/src/reanimated2/component/FlatList.tsx index 35a02192a73..e3bdb1db118 100644 --- a/src/reanimated2/component/FlatList.tsx +++ b/src/reanimated2/component/FlatList.tsx @@ -1,6 +1,6 @@ 'use strict'; import type { ForwardedRef } from 'react'; -import React, { Component, forwardRef } from 'react'; +import React, { Component, forwardRef, useRef } from 'react'; import type { FlatListProps, LayoutChangeEvent } from 'react-native'; import { FlatList } from 'react-native'; import { AnimatedView } from './View'; @@ -20,13 +20,15 @@ interface CellRendererComponentProps { } const createCellRendererComponent = ( - itemLayoutAnimation?: ILayoutAnimationBuilder + itemLayoutAnimationRef?: React.MutableRefObject< + ILayoutAnimationBuilder | undefined + > ) => { const CellRendererComponent = (props: CellRendererComponentProps) => { return ( {props.children} @@ -70,9 +72,12 @@ export const ReanimatedFlatList = forwardRef( restProps.scrollEventThrottle = 1; } + const itemLayoutAnimationRef = useRef(itemLayoutAnimation); + itemLayoutAnimationRef.current = itemLayoutAnimation; + const CellRendererComponent = React.useMemo( - () => createCellRendererComponent(itemLayoutAnimation), - [] + () => createCellRendererComponent(itemLayoutAnimationRef), + [itemLayoutAnimationRef] ); const animatedFlatList = ( From 272399c973428e490c4bdc7dce0fe987c170dd42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20B=C5=82oniarz?= Date: Mon, 13 Nov 2023 16:39:13 +0100 Subject: [PATCH 02/58] Add example --- .../examples/LayoutAnimations/ChangeTheme.tsx | 127 ++++++++++++++++++ app/src/examples/index.ts | 5 + 2 files changed, 132 insertions(+) create mode 100644 app/src/examples/LayoutAnimations/ChangeTheme.tsx diff --git a/app/src/examples/LayoutAnimations/ChangeTheme.tsx b/app/src/examples/LayoutAnimations/ChangeTheme.tsx new file mode 100644 index 00000000000..99c646de9a8 --- /dev/null +++ b/app/src/examples/LayoutAnimations/ChangeTheme.tsx @@ -0,0 +1,127 @@ +'use strict'; +import React, { useMemo, useState } from 'react'; +import { Button, StyleSheet, Text } from 'react-native'; +import Animated, { + Easing, + Layout, + RotateInUpRight, + RotateOutUpLeft, + SlideInRight, + SlideOutLeft, +} from 'react-native-reanimated'; + +const digits = [0, 1]; + +export default function ChangeThemeExample() { + return ; +} + +function List() { + const [theme, setTheme] = useState(true); + const [data, setData] = useState(digits); + const [disabled, setDisabled] = useState(false); + + const layoutAnimations = useMemo( + () => + disabled + ? { + entering: undefined, + exiting: undefined, + layout: undefined, + } + : theme + ? { + entering: RotateInUpRight, + exiting: RotateOutUpLeft, + layout: Layout.easing(Easing.exp).delay(200), + } + : { + entering: SlideInRight, + exiting: SlideOutLeft, + layout: Layout.springify().delay(200), + }, + [disabled, theme] + ); + + const backgroundColor = theme + ? { backgroundColor: 'purple' } + : { backgroundColor: 'pink' }; + + return ( + <> + Current theme: {theme ? 1 : 2} +