Reputation: 12940
I have a React Native FlatList with a ListHeaderComponent with 2 internal Text. The structure is:
This means that as the list scrolls up, Section 1 should disappear (non-sticky) whereas section 2 should stay at the top of the list (sticky).
This is the code:
<FlatList
data={ items }
renderItem={ renderItem }
ListHeaderComponent={
<View>
<Text>Section 1</Text>
<Text>Section 2</Text>
</View>
}
stickyHeaderIndices={[0]}
/>
I have to set the indices to [0] so it picks the header but there is no way to select the second within the header. Any ideas?
BTW - I thought of capturing the vertical offset as the list scrolls and then put on the HeaderComponent main <View style={{marginTop: -offset }}> so it simulates a scroll. But my understanding is that Android does not support negative margins.
BTW-2 - I am using react-native-draggable-flatlist so I wouldn't like to put the Text in the list itself as it would complicated the logic of the list items. Thanks!
Upvotes: 7
Views: 7982
Reputation: 398
try this
import React from 'react';
import { FlatList, StyleSheet, Text, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
const data = [...Array(20).keys()].map((_, index) => ({
key: `item-${index}`,
label: `Item ${index + 1}`,
}));
const ComponentA = () => (
<View style={styles.componentA}>
<Text>Component A</Text>
</View>
);
const ComponentB = () => (
<View style={styles.componentB}>
<Text>Component B</Text>
</View>
);
const MyFlatList = () => {
const renderItem = ({ item, index }) => {
if (index === 0) {
return <ComponentB />;
}
return (
<View style={styles.item}>
<Text>{item.label}</Text>
</View>
);
};
return (
<SafeAreaView>
<FlatList
data={[1, ...data]}
renderItem={renderItem}
ListHeaderComponent={<ComponentA />}
stickyHeaderIndices={[1]}
/>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
componentA: {
height: 100,
backgroundColor: 'lightblue',
justifyContent: 'center',
alignItems: 'center',
},
componentB: {
height: 100,
backgroundColor: 'lightgreen',
justifyContent: 'center',
alignItems: 'center',
},
item: {
height: 50,
justifyContent: 'center',
alignItems: 'center',
borderBottomWidth: 1,
borderBottomColor: 'gray',
backgroundColor: 'white',
},
});
export default MyFlatList;
Upvotes: 0
Reputation: 116
I just found a different way to get this done, not sure if it's best practice though, but couldn't find a different method.
Instead of placing both components inside the header, one sticky and the other not, you can add an item to the front of the array that you'll pass to the data property of the flatlist
const getData = (data: number[]) => {
const updatedData = ["sticky header", ...data]
return updatedData;
}
const YourListComponent = () => {
const someData = [1, 2, 3, 4]
return
<FlatList
stickyHeaderIndices={[1]} // 0 represents the ListHeaderComponent, 1 represents the first item in the data array
ListHeaderComponent={
<View>
<Text>Header</Text>
</View>
}
data={getData(someData)}
renderItem={({ item }) => (
<>
{typeof item === "string" && (
<View>
<Text>{item}</Text>
</View>
)}
{typeof item !== "string" && (
<View>
<Text>{item}</Text>
</View>
)}
</>
)}
/>
}
Upvotes: 2
Reputation: 23
This worked for me when trying to use SectionList
import { View, Animated, FlatList, Text } from 'react-native';
const ITEM_H = 30;
const items = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'];
const renderItem = ({ item }) => <Text style={{ height: ITEM_H }}>{item}</Text>;
export default () => {
// animated value and interpolate setting
const offset = React.useRef(new Animated.Value(0)).current;
const animValue = offset.interpolate({
inputRange: [0, ITEM_H],
outputRange: [ITEM_H, 0],
extrapolate: 'clamp',
});
// sticky second item and FlatList
return (
<SafeAreaView>
<View style={{ position: 'relative' }}>
<Animated.Text
style={{
backgroundColor: 'red',
position: 'absolute',
top: animValue,
height: ITEM_H,
zIndex: 10,
}}>
{items[1]}
</Animated.Text>
<FlatList
data={items}
renderItem={renderItem}
onScroll={Animated.event([{ nativeEvent: { contentOffset: { y: offset } } }], {
useNativeDriver: false,
})}
/>
</View>
</SafeAreaView>
);
};
Upvotes: 1
Reputation: 288
How about using Animated API?
Working example below.
import { View, Animated, FlatList, Text } from 'react-native';
const ITEM_H = 30;
const items = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'];
const renderItem = ({ item }) => <Text style={{ height: ITEM_H }}>{item}</Text>;
export default () => {
// animated value and interpolate setting
const offset = React.useRef(new Animated.Value(0)).current;
const animValue = offset.interpolate({
inputRange: [0, ITEM_H],
outputRange: [ITEM_H, 0],
extrapolate: 'clamp',
});
// sticky second item and FlatList
return (
<SafeAreaView>
<View style={{ position: 'relative' }}>
<Animated.Text
style={{
backgroundColor: 'red',
position: 'absolute',
top: animValue,
height: ITEM_H,
zIndex: 10,
}}>
{items[1]}
</Animated.Text>
<FlatList
data={items}
renderItem={renderItem}
onScroll={Animated.event([{ nativeEvent: { contentOffset: { y: offset } } }], {
useNativeDriver: false,
})}
/>
</View>
</SafeAreaView>
);
};
Upvotes: 0