Skip to content

Commit 5f4bdb0

Browse files
committed
add experimental support for layout animations
1 parent 8d9638c commit 5f4bdb0

File tree

5 files changed

+77
-3
lines changed

5 files changed

+77
-3
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ All props are spread onto underlying [FlatList](https://facebook.github.io/react
6060
| `simultaneousHandlers` | `React.Ref<any>` or `React.Ref<any>[]` | References to other gesture handlers, mainly useful when using this component within a `ScrollView`. See [Cross handler interactions](https://docs.swmansion.com/react-native-gesture-handler/docs/interactions/). |
6161
|`itemEnteringAnimation`| Reanimated `AnimationBuilder` ([docs](https://docs.swmansion.com/react-native-reanimated/docs/api/LayoutAnimations/entryAnimations)) | Animation when item is added to list.|
6262
|`itemExitingAnimation`| Reanimated `AnimationBuilder` ([docs](https://docs.swmansion.com/react-native-reanimated/docs/api/LayoutAnimations/exitAnimations))| Animation when item is removed from list.|
63-
|`itemLayoutAnimation`| Reanimated `AnimationBuilder` ([docs](https://docs.swmansion.com/react-native-reanimated/docs/api/LayoutAnimations/layoutTransitions))| Animation when list items change position.|
63+
|`itemLayoutAnimation`| Reanimated `AnimationBuilder` ([docs](https://docs.swmansion.com/react-native-reanimated/docs/api/LayoutAnimations/layoutTransitions))| Animation when list items change position (`enableLayoutAnimationExperimental` prop must be `true`).|
64+
|`enableLayoutAnimationExperimental`| `boolean`| Flag to turn on experimental support for `itemLayoutAnimation`.|
6465

6566

6667

src/components/CellRendererComponent.tsx

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import React, { useEffect, useMemo, useRef } from "react";
22
import {
3+
findNodeHandle,
34
LayoutChangeEvent,
45
MeasureLayoutOnSuccessCallback,
56
StyleProp,
67
StyleSheet,
78
ViewStyle,
89
} from "react-native";
910
import Animated, {
11+
runOnUI,
1012
useAnimatedStyle,
1113
useSharedValue,
1214
} from "react-native-reanimated";
@@ -38,6 +40,7 @@ function CellRendererComponent<T>(props: Props<T>) {
3840
activeKey,
3941
keyExtractor,
4042
horizontal,
43+
layoutAnimationDisabled,
4144
} = useDraggableFlatListContext<T>();
4245

4346
const key = keyExtractor(item, index);
@@ -124,14 +127,36 @@ function CellRendererComponent<T>(props: Props<T>) {
124127
itemLayoutAnimation,
125128
} = propsRef.current;
126129

130+
useEffect(() => {
131+
if (!propsRef.current.enableLayoutAnimationExperimental) return;
132+
const tag = findNodeHandle(viewRef.current);
133+
134+
runOnUI((t: number | null, _layoutDisabled) => {
135+
"worklet";
136+
if (!t) return;
137+
const config = global.LayoutAnimationRepository.configs[t];
138+
if (config) stashConfig(t, config);
139+
const stashedConfig = getStashedConfig(t);
140+
if (_layoutDisabled) {
141+
global.LayoutAnimationRepository.removeConfig(t);
142+
} else if (stashedConfig) {
143+
global.LayoutAnimationRepository.registerConfig(t, stashedConfig);
144+
}
145+
})(tag, layoutAnimationDisabled);
146+
}, [layoutAnimationDisabled]);
147+
127148
return (
128149
<Animated.View
129150
{...rest}
130151
ref={viewRef}
131152
onLayout={onCellLayout}
132153
entering={itemEnteringAnimation}
133154
exiting={itemExitingAnimation}
134-
layout={itemLayoutAnimation}
155+
layout={
156+
propsRef.current.enableLayoutAnimationExperimental
157+
? itemLayoutAnimation
158+
: undefined
159+
}
135160
style={[
136161
props.style,
137162
baseStyle,
@@ -151,3 +176,29 @@ const styles = StyleSheet.create({
151176
transform: [{ translateX: 0 }, { translateY: 0 }],
152177
},
153178
});
179+
180+
declare global {
181+
namespace NodeJS {
182+
interface Global {
183+
RNDFLLayoutAnimationConfigStash: Record<string, unknown>;
184+
}
185+
}
186+
}
187+
188+
runOnUI(() => {
189+
"worklet";
190+
global.RNDFLLayoutAnimationConfigStash = {};
191+
})();
192+
193+
function stashConfig(tag: number, config: unknown) {
194+
"worklet";
195+
if (!global.RNDFLLayoutAnimationConfigStash)
196+
global.RNDFLLayoutAnimationConfigStash = {};
197+
global.RNDFLLayoutAnimationConfigStash[tag] = config;
198+
}
199+
200+
function getStashedConfig(tag: number) {
201+
"worklet";
202+
if (!global.RNDFLLayoutAnimationConfigStash) return null;
203+
return global.RNDFLLayoutAnimationConfigStash[tag] as Record<string, unknown>;
204+
}

src/components/DraggableFlatList.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, {
22
useCallback,
3+
useEffect,
34
useLayoutEffect,
45
useMemo,
56
useRef,
@@ -94,6 +95,9 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
9495
} = props;
9596

9697
let [activeKey, setActiveKey] = useState<string | null>(null);
98+
const [layoutAnimationDisabled, setLayoutAnimationDisabled] = useState(
99+
!propsRef.current.enableLayoutAnimationExperimental
100+
);
97101

98102
const keyExtractor = useStableCallback((item: T, index: number) => {
99103
if (!props.keyExtractor) {
@@ -112,6 +116,19 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
112116
activeKey = null;
113117
}
114118

119+
useEffect(() => {
120+
if (!propsRef.current.enableLayoutAnimationExperimental) return;
121+
if (activeKey) {
122+
setLayoutAnimationDisabled(true);
123+
} else {
124+
// setTimeout result of trial-and-error to determine how long to wait before
125+
// re-enabling layout animations so that a drag reorder does not trigger it.
126+
setTimeout(() => {
127+
setLayoutAnimationDisabled(false);
128+
}, 100);
129+
}
130+
}, [activeKey]);
131+
115132
useLayoutEffect(() => {
116133
props.data.forEach((d, i) => {
117134
const key = keyExtractor(d, i);
@@ -328,6 +345,7 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
328345
activeKey={activeKey}
329346
keyExtractor={keyExtractor}
330347
horizontal={!!props.horizontal}
348+
layoutAnimationDisabled={layoutAnimationDisabled}
331349
>
332350
<GestureDetector gesture={panGesture}>
333351
<Animated.View

src/context/draggableFlatListContext.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ type Props<T> = {
44
activeKey: string | null;
55
keyExtractor: (item: T, index: number) => string;
66
horizontal: boolean;
7+
layoutAnimationDisabled: boolean;
78
children: React.ReactNode;
89
};
910

@@ -17,15 +18,17 @@ export default function DraggableFlatListProvider<T>({
1718
activeKey,
1819
keyExtractor,
1920
horizontal,
21+
layoutAnimationDisabled,
2022
children,
2123
}: Props<T>) {
2224
const value = useMemo(
2325
() => ({
2426
activeKey,
2527
keyExtractor,
2628
horizontal,
29+
layoutAnimationDisabled,
2730
}),
28-
[activeKey, keyExtractor, horizontal]
31+
[activeKey, keyExtractor, horizontal, layoutAnimationDisabled]
2932
);
3033

3134
return (

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export type DraggableFlatListProps<T> = Modify<
4848
itemEnteringAnimation?: AnimateProps<Animated.View>["entering"];
4949
itemExitingAnimation?: AnimateProps<Animated.View>["exiting"];
5050
itemLayoutAnimation?: AnimateProps<Animated.View>["layout"];
51+
enableLayoutAnimationExperimental?: boolean;
5152
onContainerLayout?: (params: {
5253
layout: LayoutChangeEvent["nativeEvent"]["layout"];
5354
containerRef: React.RefObject<Animated.View>;

0 commit comments

Comments
 (0)