-
-
Notifications
You must be signed in to change notification settings - Fork 67
Description
Summary
It's hard to describe the bug (so I encourage you to check the attached video), since it's prominent for animated lists.
It seems the last drawn item always stays at the top, and only gets removed when the new card is fully in the viewport.
My first thought was some zIndex
issue, but increasing the zIndex
for every upcoming item did not help.
When I increased the drawDistance
, the issue gets pushed further (so I think it's tied to the drawn content calculation). Maybe the last drawn item is duplicated? idk...
When I tested it with FlatList
, it worked as expected... When I turned off the recycling, the issue still remains. Btw, the issue was the same with FlashList
, so I'm certain it's something related to the recycling view concept.
Reproduction: https://snack.expo.dev/@jacint_fair/0fb4a5
Media
FlatList | LegendList |
---|---|
flatlist.1.mp4 |
legendlist.2.mp4 |
Reproduction, in case of expo snak is broken
import { useCallback, useMemo } from 'react';
import { Dimensions, SafeAreaView, StyleSheet, Text, View } from 'react-native';
import { AnimatedLegendList } from '@legendapp/list/reanimated';
import Animated, {
useSharedValue,
useAnimatedScrollHandler,
useAnimatedStyle,
interpolate,
Extrapolation,
} from 'react-native-reanimated';
const BACKGROUND_COLORS = ['orange', 'green', 'pink'];
const DATA = [...new Array(30)].map((_, i) => i);
const ITEM_HEIGHT = Dimensions.get('window').height;
const SEPARATOR_HEIGHT = 20;
const TOTAL_HEIGHT = ITEM_HEIGHT + SEPARATOR_HEIGHT;
const keyExtractor = (_, index) => index;
const ItemSeparatorComponent = () => {
return <View style={{ height: SEPARATOR_HEIGHT }} />;
};
export default function App() {
const scrollY = useSharedValue(0);
const renderItem = useCallback(
({ index }) => {
return <Item index={index} scrollY={scrollY} />;
},
[scrollY]
);
const scrollHandler = useAnimatedScrollHandler({
onScroll(event) {
scrollY.set(event.contentOffset.y / TOTAL_HEIGHT);
},
});
return (
<SafeAreaView style={styles.container}>
<AnimatedLegendList
data={DATA}
onScroll={scrollHandler}
renderItem={renderItem}
keyExtractor={keyExtractor}
ItemSeparatorComponent={ItemSeparatorComponent}
contentContainerStyle={{ flexGrow: 1 }}
estimatedItemSize={ITEM_HEIGHT}
snapToInterval={TOTAL_HEIGHT}
snapToAlignment="start"
decelerationRate="fast"
disableIntervalMomentum
/>
</SafeAreaView>
);
}
function Item({ index, scrollY }) {
const animatedStyle = useAnimatedStyle(() => {
return {
transform: [
{
scale: interpolate(
scrollY.value,
[index - 1, index, index + 1],
[1, 1, 0.9],
Extrapolation.CLAMP
),
},
{
translateY: interpolate(
scrollY.value,
[index - 1, index - 0.5, index, index + 1],
[0, 0, 0, TOTAL_HEIGHT],
Extrapolation.CLAMP
),
},
],
};
}, [scrollY, index]);
const backgroundColor = useMemo(() => {
return BACKGROUND_COLORS[Math.floor(Math.random() * BACKGROUND_COLORS.length)];
}, []);
return (
<Animated.View style={[styles.item, { backgroundColor }, animatedStyle]}>
<Text>{index}</Text>
</Animated.View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
backgroundColor: 'red',
padding: 8,
},
item: {
height: ITEM_HEIGHT,
width: 300,
},
});