react-nativereact-native-tab-viewreact-native-reanimated

Collapsible header in react-native-reanimated with multiple tabs in React Native


I'm trying to create a screen in React Native with 2 tabs (using react-native-tab-view) with the following layout:

 ------------------------
|   Collapsible Header   |
|------------------------|  
|    Tab A  |  Tab B     |
|------------------------| 
|                        |
|                        |
|        FlatList        |
|         in Tab         |
|         Content        |
|                        |
|                        |
|                        |
|                        |
 ------------------------

The layout I went for is absolute positioning for both the collapsible header AND the tab bar, and the FlatList actually covers the entire screen (and both the header and the tab bar are on top of it). To put the scrollable part after the tab bar, I added a paddingTop to the contentContainerStyle of the FlatList. This is the root of the issue - When scrolling in Tab A, then moving to Tab B, there will be a blank space because of that padding.

I created a complete Expo project to show this issue: https://expo.io/@bartzy/collapsible-tab-view-example This is the Github repo for the Expo project: https://github.com/bartzy/CollapsibleTabViewExample

Here's a quick video of the example app, showing the blank padding space after switching to Tab 2: enter image description here

I'd appreciate any idea on how to eliminate that blank space when moving between tabs, while keeping the desired collapsing behavior.


Solution

  • This is a common problem, there are few examples around on how to solve this, but recently I published a wrapper for the react-native-tab-view to solve this issue.

    Demo

    Example

    Example from the quick start.

    import * as React from 'react';
    import { StyleSheet, View, Text, Animated } from 'react-native';
    import {
      CollapsibleTabView,
      useCollapsibleScene,
    } from 'react-native-collapsible-tab-view';
    import { SceneMap } from 'react-native-tab-view';
    
    type Route = {
      key: string;
      title: string;
    };
    
    const SomeRoute: React.FC<{ routeKey: string; color: string }> = ({
      routeKey,
      color,
    }) => {
      const scrollPropsAndRef = useCollapsibleScene(routeKey);
    
      return (
        <Animated.ScrollView
          style={{ backgroundColor: color }}
          {...scrollPropsAndRef}
        >
          <View style={styles.content} />
        </Animated.ScrollView>
      );
    };
    
    const FirstScene = () => <SomeRoute routeKey="first" color="white" />;
    const SecondScene = () => <SomeRoute routeKey="second" color="black" />;
    
    const HEADER_HEIGHT = 250;
    
    const renderHeader = () => (
      <View style={styles.header}>
        <Text style={styles.headerText}>COLLAPSIBLE</Text>
      </View>
    );
    
    const renderScene = SceneMap({
      first: FirstScene,
      second: SecondScene,
    });
    
    const App: React.FC<object> = () => {
      const [index, setIndex] = React.useState(0);
      const [routes] = React.useState<Route[]>([
        { key: 'first', title: 'First' },
        { key: 'second', title: 'Second' },
      ]);
    
      const handleIndexChange = (index: number) => {
        setIndex(index);
      };
    
      return (
        <CollapsibleTabView<Route>
          navigationState={{ index, routes }}
          renderScene={renderScene}
          onIndexChange={handleIndexChange}
          renderHeader={renderHeader} // optional
          headerHeight={HEADER_HEIGHT} // optional, will be computed.
        />
      );
    };
    
    export default App;
    
    const styles = StyleSheet.create({
      header: {
        height: HEADER_HEIGHT,
        backgroundColor: '#2196f3',
        justifyContent: 'center',
        alignItems: 'center',
        elevation: 4,
      },
      headerText: {
        color: 'white',
        fontSize: 24,
      },
      content: {
        height: 1500,
      },
    });