PedroBern / react-native-collapsible-tab-view

A cross-platform Collapsible Tab View component for React Native
MIT License
836 stars 162 forks source link

Tabbar has low frame rates when scrolling between screens and onPress #47

Closed Stefan28BU closed 3 years ago

Stefan28BU commented 3 years ago

Current behavior

I've copied the base example project into my project, except I added the scrollEventThrottle={12} to both Tab.FlatList and Tab.ScrollView. However, the blue indicator on the tabbar has low frame rate when scrolling horizontally or on pressing.

Expected behaviour

The blue indicator should animate smoothly

Code sample

import { View, Text, StyleSheet, Animated, Easing, ListRenderItem } from 'react-native'
import * as React from 'react'
import { useTabDismissOnHideActionListener } from '../../navigation/bottom-tab-navigator-utils';
import { useCallback, useEffect, useState } from 'react';
import Colors from '../../constants/Colors';
import Layout from '../../constants/Layout';

import {
    RefComponent,
    ContainerRef,
    createCollapsibleTabs,
    TabBarProps as TabProps,
} from 'react-native-collapsible-tab-view'
import TabBar from './MaterialTabBar'

import { default as Reanimated, EasingNode, Extrapolate, useAnimatedRef } from 'react-native-reanimated'

type TabNames = 'A' | 'B'
type HeaderProps = TabProps<TabNames>

const { useTabsContext, ...Tabs } = createCollapsibleTabs<TabNames>()

const {
    add,
    interpolate,

} = Reanimated

const HEADER_HEIGHT = 250
const TABBAR_HEIGHT = 48

const ChatScreen: React.FC<{
    navigation: any
}> = (props) => {
    const {
        navigation,
    } = props;

    const {
        show,
        animated
    } = useTabDismissOnHideActionListener(navigation)

    const [focusAnimationRef] = useState(new Animated.Value(0))
    const [focusAnimationAfterRef] = useState(new Animated.Value(0))

    const containerRef = useAnimatedRef<ContainerRef>()
    const tabARef = useAnimatedRef<RefComponent>()
    const tabBRef = useAnimatedRef<RefComponent>()

    const [refMap] = React.useState({
        A: tabARef,
        B: tabBRef,
    })

    const initialize = useCallback(() => {
        Animated.sequence([
            Animated.timing(focusAnimationRef, {
                toValue: 1,
                duration: 300,
                easing: Easing.bezier(.3, 1, 1, 1),
                useNativeDriver: true,
            }),
            Animated.spring(focusAnimationAfterRef, {
                toValue: 1,
                stiffness: 300,
                damping: 200,
                mass: 1,
                useNativeDriver: true,
            })
        ]).start()
    }, [])

    const reset = useCallback(() => {
        focusAnimationRef.setValue(0)
        focusAnimationAfterRef.setValue(0)
    }, [])

    useEffect(() => {
        navigation.addListener('focus', initialize)
        navigation.addListener('blur', reset)

        return () => {
            navigation.removeListener('focus', initialize)
            navigation.removeListener('blur', reset)
        }
    }, [])

    return (
        <View style={styles.container}>
            <Animated.View style={[styles.innerContainer, {
                opacity: focusAnimationRef,
                transform: [
                    {
                        translateX: focusAnimationRef.interpolate({
                            inputRange: [0, 1],
                            outputRange: [-Layout.width, 0],
                            extrapolate: 'clamp',
                        })
                    },
                    {
                        translateY: focusAnimationAfterRef.interpolate({
                            inputRange: [0, 1],
                            outputRange: [Layout.STATUS_BAR_HEIGHT, 0],
                            // extrapolate: 'clamp',
                        })
                    },
                ]
            }]}>
                <Tabs.Container
                    containerRef={containerRef}
                    TabBarComponent={TabBar}
                    HeaderComponent={Header}
                    headerHeight={HEADER_HEIGHT}
                    tabBarHeight={TABBAR_HEIGHT}
                    refMap={refMap}
                    // diffClampEnabled snapEnabled

                >
                    <ScreenA />
                    <ScreenB />
                </Tabs.Container>
            </Animated.View>
        </View>

    );
}

const ScreenB = () => {
    return (
        <Tabs.ScrollView
            name="B"
            scrollEventThrottle={12}
            showsVerticalScrollIndicator={false}
            showsHorizontalScrollIndicator={false}
        >
            <View style={[styles.box, styles.boxA]} />
            <View style={[styles.box, styles.boxB]} />
        </Tabs.ScrollView>
    )
}

const renderItem: ListRenderItem<number> = ({ index }) => {
    return (
        <View style={[styles.box, index % 2 === 0 ? styles.boxB : styles.boxA]} />
    )
}

const ScreenA = () => {
    return (
        <Tabs.FlatList
            name="A"
            data={[0, 1, 2, 3, 4]}
            renderItem={renderItem}
            keyExtractor={(v) => v + ''}
            scrollEventThrottle={12}
            showsVerticalScrollIndicator={false}
            showsHorizontalScrollIndicator={false}
        />
    )
}

const Header: React.FC<HeaderProps> = () => {
    return <View pointerEvents={'none'} style={styles.header} />
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: Colors.themeColor,
    },
    innerContainer: {
        flex: 1,
        alignItems: 'center',
        justifyContent: 'center',
        backgroundColor: Colors.themeBlack,
        borderRadius: Layout.MODAL_BORDER_RADIUS
    },
    ////

    box: {
        height: 250,
        width: '100%',
    },
    boxA: {
        backgroundColor: 'white',
    },
    boxB: {
        backgroundColor: '#D8D8D8',
    },
    header: {
        height: HEADER_HEIGHT,
        width: '100%',
        backgroundColor: '#2196f3',
    },
})

ChatScreen.displayName = 'ChatScreen'

export default ChatScreen

Screenshots (if applicable)

What have you tried

Stefan28BU commented 3 years ago

https://user-images.githubusercontent.com/43122767/105628659-203f3880-5e79-11eb-8802-24b48038a49e.MP4

Stefan28BU commented 3 years ago

Notice the tabbar indicator has really low frame rate, Comparing to the solution I achieved using only nested scrollviews on the right profile bottom tab, however, since each screen has different height, nested scrollview would not be the best practice in my case.

Stefan28BU commented 3 years ago

No, it’s still the same. Not sure if this is a bug with Reanimated. I’m currently on RC.2, and I’m not sure if it has anything to do with the useAnimatedStyle hook. Is there any other way that I can get the screen’s scrollX other than getting it from the TabProps?

pedrobern notifications@github.com于2021年1月24日 周日下午7:40写道:

@Stefan28BU https://github.com/Stefan28BU if you set the scrollEventThrottle to 0, how does it feel? Does it get better?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/PedroBern/react-native-collapsible-tab-view/issues/47#issuecomment-766333798, or unsubscribe https://github.com/notifications/unsubscribe-auth/AKJAATYI2KMSJBGETLAALQLS3QBLNANCNFSM4WQOMMBA .

PedroBern commented 3 years ago

@Stefan28BU can you make a reproducible snack?

PedroBern commented 3 years ago

now you can get from context on v3.1

PedroBern commented 3 years ago

@Stefan28BU do you have this bug on the expo preview? Because mine is perfect.

PedroBern commented 3 years ago

@Stefan28BU I think it's fixed in v3.6.3, if you still have this issue please let me know.