首页 > 解决方案 > 我们如何使用 ScrollView 和 FlatList 使标签视图与 react-native-tab-view 保持粘性?

问题描述

我是 React Native 的新手,正在尝试使用react-native-tab-view. 我想要实现的就像 Twitter Profile UI。

这是我到目前为止所做的:

粘性标题

我想要实现的是当用户向下滚动时,标签栏将粘在动画标题上。当用户再次向上滚动时,标签会慢慢回到之前的位置。

我已经尝试了很多方法来做到这一点,但我无法很好地解决这个问题。

这是代码

import ...

const WIDTH = Dimensions.get('screen').width;
const initialLayout = WIDTH;
const HEADER_MAX_HEIGHT = 120;
const HEADER_MIN_HEIGHT = 70;
const PROFILE_IMAGE_MAX_HEIGHT = 80;
const PROFILE_IMAGE_MIN_HEIGHT = 40;
const imgData = [
      // ...
];
const NUM_COLUMNS = 3;
const ProfileScreen = (props) => {

    const [index, setIndex] = useState(0);
    const [isFollowed, setIsFollowed] = useState(false);
    const [enableScrollView, setEnableScrollView] = useState(false);
    const [routes] = React.useState([
      { key: 'post', title: 'Posts'},
      { key: 'trip', title: 'Blogs'},
    ]);
  
    const renderScene = SceneMap({
      post: PostView,
      trip: TripView,
    });
    const scrollY = useRef(new Animated.Value(0)).current;
    const headerHeight = scrollY.interpolate({
        inputRange: [0, HEADER_MAX_HEIGHT - HEADER_MIN_HEIGHT], // [0, 50]
        outputRange: [HEADER_MAX_HEIGHT, HEADER_MIN_HEIGHT], // [120,70]
        extrapolate: 'clamp'
    });
    const profileImageHeight = scrollY.interpolate({
        inputRange: [0, HEADER_MAX_HEIGHT - HEADER_MIN_HEIGHT],
        outputRange: [PROFILE_IMAGE_MAX_HEIGHT, PROFILE_IMAGE_MIN_HEIGHT],
        extrapolate: 'clamp'
    });
  
    const profileImageMarginTop = scrollY.interpolate({
        inputRange: [0, HEADER_MAX_HEIGHT - HEADER_MIN_HEIGHT],
        outputRange: [
          HEADER_MAX_HEIGHT - PROFILE_IMAGE_MAX_HEIGHT / 2,
          HEADER_MAX_HEIGHT + 5
        ],
        extrapolate: 'clamp'
    });
    const headerZindex = scrollY.interpolate({
        inputRange: [0, HEADER_MAX_HEIGHT - HEADER_MIN_HEIGHT, 120],
        outputRange: [0, 0, 1000],
        extrapolate: 'clamp'
    });

    const profile = useSelector(state => state.profile.userProfile );

    return(
        <SafeAreaView style={styles.container}>
            <Animated.View style={[styles.backgroundImage, {
                height: headerHeight,
                zIndex: headerZindex,
                elevation: headerZindex, //required for android
            }]}>
            </Animated.View>
            
            <ScrollView 
                style={{flex: 1}}
                scrollEventThrottle={16}
                onScroll={Animated.event(
                    [{nativeEvent: {contentOffset: {y: scrollY}}}],
                )}
                bounces={true}
                nestedScrollEnabled={true}
            >
                <View style={{overflow: 'hidden', position: 'absolute', top: HEADER_MIN_HEIGHT/2, left: HEADER_MIN_HEIGHT*2.6}}>
                    <TouchableOpacity><Icon color='white' name='camera-retro' size={30} /></TouchableOpacity>
                </View>
                <Animated.View style={[styles.profileImgContainer,{
                    height: profileImageHeight,
                    width: profileImageHeight,
                    marginTop: profileImageMarginTop,
                    borderRadius: PROFILE_IMAGE_MAX_HEIGHT/2,
                }]}>                         
                    <Image source={{uri: profile.userAvatar}} style={styles.profileImg} />
                </Animated.View>

                [...]

                    <TabView
                        style={{
                            flex: 4.2,
                        }}
                        navigationState={{ index, routes }}
                        renderScene={renderScene}
                        onIndexChange={setIndex}
                        initialLayout={initialLayout}
                        renderTabBar={props => {
                            return(
                                <Animated.View style={{
                                    position: 'absolute',
                                    top: 0,
                                    left: 0,
                                    right: 0,
                                    zIndex: headerZindex,
                                    elevation: headerZindex,
                                    // transform: [{ translateY: tabBarHeader }],
                                    // marginTop: tabBarHeader
                                }}>
                                    <TabBar {...props} />
                                </Animated.View>
                            )
                        }}
                    />
            </ScrollView>
        </SafeAreaView>
    );
};
const PostView = props => {
    return(
        <View>
            <FlatList 
                style={{marginTop: 53}}
                data={imgData}
                numColumns={NUM_COLUMNS}
                keyExtractor={item => item.id}
                renderItem={(itemData) => (
                        <TouchableOpacity>
                            <Image source={itemData.item.source} style={{height:WIDTH/3, width:WIDTH/3, borderColor: 'white', borderWidth: 2}}/>
                        </TouchableOpacity>
                )}
                            
            />
        </View>
    )
}
const TripView = props => {
    return(
        <Button title="Trip Display" />
    )
} 
export default ProfileScreen;

const styles = StyleSheet.create({
    //...

})

我还遇到了错误:“虚拟化列表永远不应该嵌套在普通的 ScrollView 中”,并找到了类似ListHeaderComponent或使用的道具onStartShouldSetResponderCapture

但我无法找出适当的进展来实施这些,或者准确地说,我不知道下一步该去哪里

请帮我。非常感谢!

标签: javascriptreactjsreact-nativeanimationreact-native-tab-view

解决方案


推荐阅读