How to make a part of ListHeaderComponent sticky on React Native FlatList

Viewed 1851

I have a React Native FlatList with a ListHeaderComponent with 2 internal Text. The structure is:

  • header
    • section 1 - non sticky
    • section 2 - sticky
  • list items

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!

3 Answers

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>
  );
};

How about using Animated API?

  1. You basically set the fixed height for each items.
  2. Only the second item (sticky one) is outside of the FlatList and positioned absolute.
  3. Sticky item has its offset as the item height.
  4. As you scroll up, the offset value will be interpolated into zero, which means sticks at the top.

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>
  );
};

You need to set prop to Flatlist as stickyHeaderIndices={[0]}

Related