Skip to content

Commit 355edf9

Browse files
committed
perf: do not animate outside visible range
1 parent abf9105 commit 355edf9

File tree

4 files changed

+62
-21
lines changed

4 files changed

+62
-21
lines changed

src/components/DraggableFlatList.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ type RNGHFlatListProps<T> = Animated.AnimateProps<
3535
}
3636
>;
3737

38+
type OnViewableItemsChangedCallback<T> = Exclude<
39+
FlatListProps<T>["onViewableItemsChanged"],
40+
undefined | null
41+
>;
42+
3843
const AnimatedFlatList = (Animated.createAnimatedComponent(
3944
FlatList
4045
) as unknown) as <T>(props: RNGHFlatListProps<T>) => React.ReactElement;
@@ -62,6 +67,8 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
6267
autoScrollDistance,
6368
panGestureState,
6469
isTouchActiveNative,
70+
viewableIndexMin,
71+
viewableIndexMax,
6572
disabled,
6673
} = useAnimatedValues();
6774

@@ -287,6 +294,21 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
287294

288295
useAutoScroll();
289296

297+
const onViewableItemsChanged = useStableCallback<
298+
OnViewableItemsChangedCallback<T>
299+
>((info) => {
300+
const viewableIndices = info.viewableItems
301+
.filter((item) => item.isViewable)
302+
.map((item) => item.index)
303+
.filter((index): index is number => typeof index === "number");
304+
305+
const min = Math.min(...viewableIndices);
306+
const max = Math.max(...viewableIndices);
307+
viewableIndexMin.value = min;
308+
viewableIndexMax.value = max;
309+
props.onViewableItemsChanged?.(info);
310+
});
311+
290312
return (
291313
<DraggableFlatListProvider
292314
activeKey={activeKey}
@@ -305,6 +327,7 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
305327
<AnimatedFlatList
306328
{...props}
307329
data={props.data}
330+
onViewableItemsChanged={onViewableItemsChanged}
308331
CellRendererComponent={CellRendererComponent}
309332
ref={flatlistRef}
310333
onContentSizeChange={onListContentSizeChange}

src/context/animatedValueContext.tsx

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export function useAnimatedValues() {
3737
function useSetupAnimatedValues<T>() {
3838
const props = useProps<T>();
3939

40-
const DEFAULT_VAL = useSharedValue(0)
40+
const DEFAULT_VAL = useSharedValue(0);
4141

4242
const containerSize = useSharedValue(0);
4343
const scrollViewSize = useSharedValue(0);
@@ -63,18 +63,25 @@ function useSetupAnimatedValues<T>() {
6363
const scrollOffset = useSharedValue(0);
6464
const scrollInit = useSharedValue(0);
6565

66+
const viewableIndexMin = useSharedValue(0);
67+
const viewableIndexMax = useSharedValue(0);
68+
6669
// If list is nested there may be an outer scrollview
6770
const outerScrollOffset = props.outerScrollOffset || DEFAULT_VAL;
6871
const outerScrollInit = useSharedValue(0);
6972

70-
useAnimatedReaction(() => {
71-
return activeIndexAnim.value
72-
}, (cur, prev) => {
73+
useAnimatedReaction(
74+
() => {
75+
return activeIndexAnim.value;
76+
},
77+
(cur, prev) => {
7378
if (cur !== prev && cur >= 0) {
7479
scrollInit.value = scrollOffset.value;
7580
outerScrollInit.value = outerScrollOffset.value;
7681
}
77-
}, [outerScrollOffset]);
82+
},
83+
[outerScrollOffset]
84+
);
7885

7986
const placeholderOffset = useSharedValue(0);
8087

@@ -83,10 +90,10 @@ function useSetupAnimatedValues<T>() {
8390
}, []);
8491

8592
const autoScrollDistance = useDerivedValue(() => {
86-
if (!isDraggingCell.value) return 0
93+
if (!isDraggingCell.value) return 0;
8794
const innerScrollDiff = scrollOffset.value - scrollInit.value;
8895
// If list is nested there may be an outer scroll diff
89-
const outerScrollDiff = outerScrollOffset.value - outerScrollInit.value
96+
const outerScrollDiff = outerScrollOffset.value - outerScrollInit.value;
9097
const scrollDiff = innerScrollDiff + outerScrollDiff;
9198
return scrollDiff;
9299
}, []);
@@ -99,7 +106,6 @@ function useSetupAnimatedValues<T>() {
99106
}, []);
100107

101108
const touchPositionDiffConstrained = useDerivedValue(() => {
102-
103109
const containerMinusActiveCell =
104110
containerSize.value - activeCellSize.value + scrollOffset.value;
105111

@@ -122,7 +128,6 @@ function useSetupAnimatedValues<T>() {
122128
return props.dragItemOverflow
123129
? touchPositionDiff.value
124130
: touchPositionDiffConstrained.value;
125-
126131
}, []);
127132

128133
const hoverOffset = useDerivedValue(() => {
@@ -164,6 +169,8 @@ function useSetupAnimatedValues<T>() {
164169
touchPositionDiff,
165170
touchTranslate,
166171
autoScrollDistance,
172+
viewableIndexMin,
173+
viewableIndexMax,
167174
}),
168175
[
169176
activeCellOffset,
@@ -185,9 +192,11 @@ function useSetupAnimatedValues<T>() {
185192
touchPositionDiff,
186193
touchTranslate,
187194
autoScrollDistance,
195+
viewableIndexMin,
196+
viewableIndexMax,
188197
]
189198
);
190-
199+
191200
useEffect(() => {
192201
props.onAnimValInit?.(value);
193202
}, [value]);

src/hooks/useCellTranslate.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,23 @@ export function useCellTranslate({ cellIndex, cellSize, cellOffset }: Params) {
1717
spacerIndexAnim,
1818
placeholderOffset,
1919
hoverAnim,
20+
viewableIndexMin,
21+
viewableIndexMax,
2022
} = useAnimatedValues();
2123

2224
const { activeKey } = useDraggableFlatListContext();
2325

2426
const { animationConfigRef } = useRefs();
2527

2628
const translate = useDerivedValue(() => {
27-
if (!activeKey || activeIndexAnim.value < 0) return 0;
29+
const isActiveCell = cellIndex === activeIndexAnim.value;
30+
const isOutsideViewableRange =
31+
!isActiveCell &&
32+
(cellIndex < viewableIndexMin.value ||
33+
cellIndex > viewableIndexMax.value);
34+
if (!activeKey || activeIndexAnim.value < 0 || isOutsideViewableRange) {
35+
return 0;
36+
}
2837

2938
// Determining spacer index is hard to visualize. See diagram: https://i.imgur.com/jRPf5t3.jpg
3039
const isBeforeActive = cellIndex < activeIndexAnim.value;
@@ -80,7 +89,7 @@ export function useCellTranslate({ cellIndex, cellSize, cellOffset }: Params) {
8089
if (activeIndexAnim.value < 0) return 0;
8190

8291
// Active cell follows touch
83-
if (cellIndex === activeIndexAnim.value) {
92+
if (isActiveCell) {
8493
return hoverAnim.value;
8594
}
8695

@@ -96,7 +105,7 @@ export function useCellTranslate({ cellIndex, cellSize, cellOffset }: Params) {
96105
: 0;
97106

98107
return withSpring(translationAmt, animationConfigRef.current);
99-
}, [activeKey]);
108+
}, [activeKey, cellIndex]);
100109

101110
return translate;
102111
}

src/hooks/useStableCallback.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ import { useRef, useCallback } from "react";
55
// Useful for functions that depend on external state, but
66
// should not trigger effects when that external state changes.
77

8-
export function useStableCallback<T extends (a?: any, b?: any, c?: any) => any>(
9-
fn: T
10-
) {
11-
const fnRef = useRef(fn);
12-
fnRef.current = fn;
13-
const identityRetainingFn = useCallback(
14-
(...args: Parameters<T>) => fnRef.current(...args),
8+
export function useStableCallback<
9+
T extends (arg1?: any, arg2?: any, arg3?: any) => any
10+
>(cb: T) {
11+
const cbRef = useRef(cb);
12+
cbRef.current = cb;
13+
const identityRetainingCb = useCallback(
14+
(...args: Parameters<T>) => cbRef.current(...args),
1515
[]
1616
);
17-
return identityRetainingFn as T;
17+
return identityRetainingCb as T;
1818
}

0 commit comments

Comments
 (0)