Skip to content

Instantly share code, notes, and snippets.

@EQuimper
Forked from maitham/Collapsible Tab View
Created June 19, 2020 15:21
Show Gist options
  • Save EQuimper/dd5a4d6bae369c5dce0898cde926b340 to your computer and use it in GitHub Desktop.
Save EQuimper/dd5a4d6bae369c5dce0898cde926b340 to your computer and use it in GitHub Desktop.

Revisions

  1. EQuimper renamed this gist Jun 19, 2020. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  2. @maitham maitham revised this gist Jun 6, 2020. 1 changed file with 10 additions and 6 deletions.
    16 changes: 10 additions & 6 deletions Collapsible Tab View
    Original file line number Diff line number Diff line change
    @@ -4,7 +4,7 @@ import { TabView } from 'react-native-tab-view';
    import { TabScreen } from './Tab';
    import { CustomTabBar } from './TabBar';
    import { ThemeContext } from 'react-native-elements';

    import { useHeaderHeight } from '@react-navigation/stack';
    const AnimatedHeader = ({ style, content }) => {
    return <Animated.View style={style}>{content}</Animated.View>;
    };
    @@ -17,6 +17,8 @@ const initialLayout = {
    const HEADER_HEIGHT = 320;
    const TABBAR_HEIGHT = 48;

    const TOTAL_HEADER_HEIGHT = HEADER_HEIGHT + TABBAR_HEIGHT;

    export const Tab: React.FC = ({ routeDefinitions, header, dataMap }) => {
    const {
    theme: { colors },
    @@ -30,6 +32,7 @@ export const Tab: React.FC = ({ routeDefinitions, header, dataMap }) => {
    extrapolate: 'clamp',
    }),
    ).current;
    const navHeaderHeight = useHeaderHeight();

    // react-native-tab-view
    const [index, setIndex] = useState(0);
    @@ -78,15 +81,18 @@ export const Tab: React.FC = ({ routeDefinitions, header, dataMap }) => {
    };

    const renderScene = ({ route }) => {
    const contentInset = Math.max(initialLayout.height - dataMap[route.key].length * 65, 0);
    const contentInset = Math.max(
    initialLayout.height - dataMap[route.key].length * 75 - TABBAR_HEIGHT - navHeaderHeight,
    0,
    );
    return (
    <TabScreen
    ref={(ref) => getFlatListRefs(ref, route)}
    data={dataMap[route.key]}
    itemType={route.key}
    contentInset={contentInset === 0 ? 0 : 240}
    contentInset={contentInset === 0 ? 0 : contentInset}
    scrollContentContainerStyle={{
    paddingTop: HEADER_HEIGHT + TABBAR_HEIGHT,
    paddingTop: TOTAL_HEADER_HEIGHT,
    }}
    onScroll={Animated.event([{ nativeEvent: { contentOffset: { y: scrollY } } }], {
    useNativeDriver: true,
    @@ -152,8 +158,6 @@ export const Tab: React.FC = ({ routeDefinitions, header, dataMap }) => {
    };




    export const TabScreen: React.FC<TabProps> = React.memo(
    React.forwardRef(
    (
  3. @maitham maitham revised this gist Jun 6, 2020. 1 changed file with 42 additions and 0 deletions.
    42 changes: 42 additions & 0 deletions Collapsible Tab View
    Original file line number Diff line number Diff line change
    @@ -150,3 +150,45 @@ export const Tab: React.FC = ({ routeDefinitions, header, dataMap }) => {
    </>
    );
    };




    export const TabScreen: React.FC<TabProps> = React.memo(
    React.forwardRef(
    (
    {
    scrollContentContainerStyle,
    onScroll,
    onScrollEndDrag,
    onMomentumScrollEnd,
    data,
    itemType,
    contentInset,
    },
    ref,
    ) => {
    return (
    <AnimatedFlatList
    ref={ref}
    data={data}
    renderItem={({ item }) => <Item item={item} itemType={itemType} />}
    keyExtractor={keyExtractor}
    scrollEventThrottle={1}
    contentInset={{ bottom: contentInset }}
    onScroll={onScroll}
    showsVerticalScrollIndicator={false}
    onScrollEndDrag={onScrollEndDrag}
    onMomentumScrollEnd={onMomentumScrollEnd}
    contentContainerStyle={[{}, scrollContentContainerStyle]}
    />
    );
    },
    ),
    );

    function keyExtractor(item: any, index: number): string {
    return index.toString();
    }


  4. @maitham maitham created this gist Jun 6, 2020.
    152 changes: 152 additions & 0 deletions Collapsible Tab View
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,152 @@
    import React, { useState, useRef, useContext, useCallback } from 'react';
    import { Dimensions, View, Animated } from 'react-native';
    import { TabView } from 'react-native-tab-view';
    import { TabScreen } from './Tab';
    import { CustomTabBar } from './TabBar';
    import { ThemeContext } from 'react-native-elements';

    const AnimatedHeader = ({ style, content }) => {
    return <Animated.View style={style}>{content}</Animated.View>;
    };

    const initialLayout = {
    width: Dimensions.get('window').width,
    height: Dimensions.get('window').height,
    };

    const HEADER_HEIGHT = 320;
    const TABBAR_HEIGHT = 48;

    export const Tab: React.FC = ({ routeDefinitions, header, dataMap }) => {
    const {
    theme: { colors },
    } = useContext(ThemeContext);
    const tabRef = useRef(null);
    const scrollY = useRef(new Animated.Value(0)).current;
    const headerTranslateY = useRef(
    scrollY.interpolate({
    inputRange: [0, HEADER_HEIGHT],
    outputRange: [0, -HEADER_HEIGHT],
    extrapolate: 'clamp',
    }),
    ).current;

    // react-native-tab-view
    const [index, setIndex] = useState(0);
    const routes = routeDefinitions;
    const listRefs = useRef([]);
    const listRefsOffsets = useRef({});

    const getFlatListRefs = (ref, route) => {
    if (ref) {
    const found = listRefs.current.find((e) => e.key === route.key);
    if (!found) {
    listRefs.current.push({
    key: route.key,
    value: ref,
    });
    }
    }
    };

    const scrollTabToOffset = (item, y) => {
    item.value.scrollToOffset({
    offset: y,
    animated: false,
    });
    };

    const syncHiddenTab = (hiddenTab, currentTabOffsets) => {
    const offsets = listRefsOffsets.current[hiddenTab.key];
    if (!offsets?.offset && currentTabOffsets.offset > HEADER_HEIGHT) {
    scrollTabToOffset(hiddenTab, HEADER_HEIGHT);
    } else if (currentTabOffsets.offset >= HEADER_HEIGHT) {
    scrollTabToOffset(hiddenTab, HEADER_HEIGHT);
    } else if (currentTabOffsets.offset < HEADER_HEIGHT) {
    scrollTabToOffset(hiddenTab, currentTabOffsets.offset);
    }
    };

    const syncOffset = () => {
    const curRouteKey = routes[index].key;
    const currentTabOffsets = listRefsOffsets.current[curRouteKey];
    listRefs.current.forEach((item) => {
    if (item.key !== curRouteKey) {
    syncHiddenTab(item, currentTabOffsets);
    }
    });
    };

    const renderScene = ({ route }) => {
    const contentInset = Math.max(initialLayout.height - dataMap[route.key].length * 65, 0);
    return (
    <TabScreen
    ref={(ref) => getFlatListRefs(ref, route)}
    data={dataMap[route.key]}
    itemType={route.key}
    contentInset={contentInset === 0 ? 0 : 240}
    scrollContentContainerStyle={{
    paddingTop: HEADER_HEIGHT + TABBAR_HEIGHT,
    }}
    onScroll={Animated.event([{ nativeEvent: { contentOffset: { y: scrollY } } }], {
    useNativeDriver: true,
    listener: (event) => {
    listRefsOffsets.current[route.key] = { offset: event.nativeEvent.contentOffset.y };
    },
    })}
    onScrollEndDrag={() => syncOffset()}
    onMomentumScrollEnd={() => syncOffset()}
    />
    );
    };

    const AnimatedTabBar = (props) => (
    <Animated.View
    style={{
    position: 'absolute',
    top: HEADER_HEIGHT,
    left: 0,
    right: 0,
    zIndex: 1,
    paddingLeft: 8,
    paddingBottom: 10,
    transform: [{ translateY: headerTranslateY }],
    backgroundColor: colors.background,
    }}
    >
    <CustomTabBar
    tabBarStyle={{ marginTop: 10 }}
    currentIndex={index}
    onIndexChange={setIndex}
    navigationState={props.navigationState}
    ref={tabRef}
    />
    </Animated.View>
    );

    return (
    <>
    <AnimatedHeader
    style={{
    position: 'absolute',
    height: HEADER_HEIGHT,
    transform: [{ translateY: headerTranslateY }],
    zIndex: 1,
    backgroundColor: colors.background,
    }}
    content={header}
    />

    <View style={{ flex: 1 }}>
    <TabView
    navigationState={{ index, routes }}
    renderScene={renderScene}
    renderTabBar={(props) => <AnimatedTabBar {...props} />}
    initialLayout={initialLayout}
    onIndexChange={setIndex}
    style={{ overflow: 'visible' }}
    />
    </View>
    </>
    );
    };