Skip to content

Commit 7b2b9db

Browse files
committed
attempt to fix occasional flicker on drag end
1 parent c0f3e57 commit 7b2b9db

File tree

3 files changed

+51
-27
lines changed

3 files changed

+51
-27
lines changed

src/components/CellRendererComponent.tsx

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
LayoutChangeEvent,
44
MeasureLayoutOnSuccessCallback,
55
StyleProp,
6+
StyleSheet,
67
ViewStyle,
78
} from "react-native";
89
import Animated, {
@@ -42,6 +43,7 @@ function CellRendererComponent<T>(props: Props<T>) {
4243
const key = keyExtractor(item, index);
4344
const offset = useSharedValue(-1);
4445
const size = useSharedValue(-1);
46+
const heldTanslate = useSharedValue(0);
4547

4648
const translate = useCellTranslate({
4749
cellOffset: offset,
@@ -52,14 +54,17 @@ function CellRendererComponent<T>(props: Props<T>) {
5254
const isActive = activeKey === key;
5355

5456
const animStyle = useAnimatedStyle(() => {
57+
// When activeKey becomes null at the end of a drag and the list reorders,
58+
// the animated style may apply before the next paint, causing a flicker.
59+
// Solution is to hold over the last animated value until the next onLayout.
60+
if (translate.value) {
61+
heldTanslate.value = translate.value;
62+
}
63+
const t = activeKey ? translate.value : heldTanslate.value;
5564
return {
56-
transform: [
57-
horizontalAnim.value
58-
? { translateX: translate.value }
59-
: { translateY: translate.value },
60-
],
65+
transform: [horizontalAnim.value ? { translateX: t } : { translateY: t }],
6166
};
62-
}, [translate]);
67+
}, [translate, activeKey]);
6368

6469
const updateCellMeasurements = useStableCallback(() => {
6570
const onSuccess: MeasureLayoutOnSuccessCallback = (x, y, w, h) => {
@@ -91,6 +96,7 @@ function CellRendererComponent<T>(props: Props<T>) {
9196
});
9297

9398
const onCellLayout = useStableCallback((e?: LayoutChangeEvent) => {
99+
heldTanslate.value = 0;
94100
updateCellMeasurements();
95101
if (onLayout && e) onLayout(e);
96102
});
@@ -112,15 +118,16 @@ function CellRendererComponent<T>(props: Props<T>) {
112118
};
113119
}, [isActive, horizontal]);
114120

115-
// changing zIndex may crash android, but seems to work ok as of RN 68:
116-
// https://github.com/facebook/react-native/issues/28751
117-
118121
return (
119122
<Animated.View
120123
{...rest}
121124
ref={viewRef}
122125
onLayout={onCellLayout}
123-
style={[props.style, baseStyle, animStyle]}
126+
style={[
127+
props.style,
128+
baseStyle,
129+
activeKey ? animStyle : styles.zeroTranslate,
130+
]}
124131
pointerEvents={activeKey ? "none" : "auto"}
125132
>
126133
<CellProvider isActive={isActive}>{children}</CellProvider>
@@ -129,3 +136,9 @@ function CellRendererComponent<T>(props: Props<T>) {
129136
}
130137

131138
export default typedMemo(CellRendererComponent);
139+
140+
const styles = StyleSheet.create({
141+
zeroTranslate: {
142+
transform: [{ translateX: 0 }, { translateY: 0 }],
143+
},
144+
});

src/components/DraggableFlatList.tsx

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import React, { useCallback, useLayoutEffect, useMemo, useState } from "react";
1+
import React, {
2+
useCallback,
3+
useLayoutEffect,
4+
useMemo,
5+
useRef,
6+
useState,
7+
} from "react";
28
import { ListRenderItem, FlatListProps, LayoutChangeEvent } from "react-native";
39
import {
410
FlatList,
@@ -72,13 +78,22 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
7278
disabled,
7379
} = useAnimatedValues();
7480

81+
const reset = useStableCallback(() => {
82+
activeIndexAnim.value = -1;
83+
spacerIndexAnim.value = -1;
84+
touchTranslate.value = 0;
85+
activeCellSize.value = -1;
86+
activeCellOffset.value = -1;
87+
setActiveKey(null);
88+
});
89+
7590
const {
7691
dragHitSlop = DEFAULT_PROPS.dragHitSlop,
7792
scrollEnabled = DEFAULT_PROPS.scrollEnabled,
7893
activationDistance: activationDistanceProp = DEFAULT_PROPS.activationDistance,
7994
} = props;
8095

81-
const [activeKey, setActiveKey] = useState<string | null>(null);
96+
let [activeKey, setActiveKey] = useState<string | null>(null);
8297

8398
const keyExtractor = useStableCallback((item: T, index: number) => {
8499
if (!props.keyExtractor) {
@@ -87,6 +102,16 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
87102
return props.keyExtractor(item, index);
88103
});
89104

105+
const dataRef = useRef(props.data);
106+
const dataHasChanged =
107+
dataRef.current.map(keyExtractor).join("") !==
108+
props.data.map(keyExtractor).join("");
109+
dataRef.current = props.data;
110+
if (dataHasChanged) {
111+
// When data changes make sure `activeKey` is nulled out in the same render pass
112+
activeKey = null;
113+
}
114+
90115
useLayoutEffect(() => {
91116
props.data.forEach((d, i) => {
92117
const key = keyExtractor(d, i);
@@ -178,19 +203,8 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
178203
newData.splice(to, 0, data[from]);
179204
}
180205

181-
const reset = () => {
182-
activeIndexAnim.value = -1;
183-
spacerIndexAnim.value = -1;
184-
touchTranslate.value = 0;
185-
activeCellSize.value = -1;
186-
activeCellOffset.value = -1;
187-
setActiveKey(null);
188-
};
189-
190-
if (isWeb) reset();
191-
else setTimeout(reset);
192-
193206
onDragEnd?.({ from, to, data: newData });
207+
reset();
194208
}
195209
);
196210

src/hooks/useCellTranslate.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,6 @@ export function useCellTranslate({ cellIndex, cellSize, cellOffset }: Params) {
8585
placeholderOffset.value = newPlaceholderOffset;
8686
}
8787

88-
// If no active cell, translation is already 0
89-
if (activeIndexAnim.value < 0) return 0;
90-
9188
// Active cell follows touch
9289
if (isActiveCell) {
9390
return hoverAnim.value;

0 commit comments

Comments
 (0)