美文网首页
react-native页面滑动时添加顶部悬浮Navbar、添加

react-native页面滑动时添加顶部悬浮Navbar、添加

作者: sunny635533 | 来源:发表于2023-12-14 11:49 被阅读0次

    react-native页面滑动时添加顶部悬浮Navbar、添加悬浮组件跟随某个组件位置,
    效果如图:


    110_1702611282 (1).gif

    关键代码如下:

    //1、使用Animated.FlatList的onScroll事件
    <Animated.FlatList
           style={{ flex: 1 }}
           contentContainerStyle={{
             paddingBottom: ScreenUtils.safeBottom,
             backgroundColor: '#fff',
           }}
           showsVerticalScrollIndicator={false}
           data={dataList}
           ListHeaderComponent={this.renderHeader}
           renderItem={this.renderItem}
           onRefresh={this.onRefresh}
           onEndReached={this.onEndReached}
           refreshing={refreshing}
           keyExtractor={(__, k) => `${k}`}
           ListEmptyComponent={loadEnd && <Footer noType={"no_hot_product"} />}
           ListFooterComponent={isLoadMore && <ListFooter />}
           onScroll={Animated.event([
             {
               nativeEvent: { contentOffset: { y: this.scrollY } }
             }], { useNativeDriver: false })
           }
         />
    
    //2、添加需要悬浮显示组件,其中“headerHeight - 90 - ScreenUtils.headerHeight”,90是两个tab的组件高度,ScreenUtils.headerHeight是状态栏和导航栏的高度。
    <Animated.View
           style={{
             position: 'absolute',
             top: 0,
             opacity: this.scrollY.interpolate({
               inputRange: [-1, 0, 60],
               outputRange: [0, 0, 1],
             })
           }}>
           <NavBar backgroundColor={"#fff"} hideBottomLine={true}>{"标题"}</NavBar>
           {headerHeight > 0 && <Animated.View
             style={{
               width: ScreenUtils.width,
               position: 'absolute',
               backgroundColor: '#fff',
               opacity: this.scrollY.interpolate({
                 inputRange: [0, headerHeight - 90 - ScreenUtils.headerHeight, headerHeight - 90 - ScreenUtils.headerHeight + 1],
                 outputRange: [0, 0, 1],
               }),
               top: this.scrollY.interpolate({
                 inputRange: [0, headerHeight - 90 - ScreenUtils.headerHeight, headerHeight - 90 - ScreenUtils.headerHeight + 1],
                 outputRange: [-120, ScreenUtils.headerHeight, ScreenUtils.headerHeight],
               })
             }}>
             {this.renderTabView({ marginTop: 0 })}
           </Animated.View>}
         </Animated.View>
    {this.renderFloatModal()}
    

    注意,因为悬浮组件和固定组件是两个相同组件,如果在tab组件上添加滑动效果的时候要注意让两个tab都跟着滑动:

    firstTabScrollViewRef1 = React.createRef();
      firstTabScrollViewRef2 = React.createRef();
    
    scrollToIndex = (index) => {
        const layout = this.layoutList[index];
        if (!layout) {
          return;
        }
        if (this.firstTabScrollViewRef1.current && this.firstTabScrollViewRef2.current) {
          //先算出tab的中心点,再算出scrollview实际需要滑动的距离(scrollview左边的left位置-屏幕中间位置=实际超出屏幕的位置)
          const left = layout.x + (layout.width) / 2;
          const x = left - (ScreenUtils.width / 2);
          this.firstTabScrollViewRef1.current.scrollTo({ x: x, animated: true });
          this.firstTabScrollViewRef2.current.scrollTo({ x: x, animated: true });
        }
      } 
    
    {this.renderTabView(this.firstTabScrollViewRef1)}
    
    {this.renderTabView(this.firstTabScrollViewRef2, { marginTop: 0 })}
    
    

    以下是该页面全部代码,引用的组件可以自行替换后测试:

    import React from "react";
    import {
      View,
      StyleSheet,
      Text,
      ImageBackground,
      Animated,
      ScrollView,
    } from "react-native";
    import { Footer, NavBar, } from "../../../component/All";
    import { I18n } from '@lang';
    import { UIButton, UIButtonWithImage, UIButtonWithSingleText } from "../../../component/UIButton";
    import ScreenUtils from "../../../utils/ScreenUtils";
    import ListFooter from "../../../component/ListFooter";
    import { ListItemView } from "./component/ListItemView";
    
    /**
     * 会场样式三,顶部有图片、标题、两个tab显示(第一个tab显示类目,第二个tab是日月周榜)
     */
    export default class MeetPlaceThirdPage extends React.Component {
      scrollY = new Animated.Value(0);
      topHeight = new Animated.Value(0);
    
      hasMore = false;
      params = {
        page: 1,//页数
        limit: 20,//每页的条数
        type: 1,//类型 0:总榜 1:日榜 2:周榜 3:月榜
      };
    
      constructor(props) {
        super(props);
        this.state = {
          firstTabIndex: 0,
          firstTabList: [{ name: "综合" },
          { name: "商品类目一" },
          { name: "商品类目二" },
          { name: "商品类目二" },
          { name: "商品类目二" },
          { name: "商品类目二" },
          { name: "商品类目二" },
          ],
          secondTabIndex: 0,
          secondTabList: [I18n.t('day'), I18n.t('week'), I18n.t('all')],
          headerHeight: 0,//用于固定tab高度
          showFloatModal: false,//是否显示悬浮的类目弹窗
    
          refreshing: false,
          dataList: [0, 1, 1, 1, 1, , 11, 1, 1, 1, 1, 1, 1,],
          loadEnd: false,// 是否首次加载完毕
          isLoadMore: false,//是否加载更多
        }
      }
    
      componentDidMount() {
        this.onRefresh(false)
      }
    
      /**
       * @param {*} parentId 
       */
      getCategoryList = async (parentId = 0) => {
        const params = { parentId };//parentId 父级分类编号 0:一级分类;
        let list = [];
        try {
          list = await api_goods_sub_type_list(params);
          const newList = [{ name: I18n.t('all') }].concat(list);
          this.setState({ firstTabList: newList });
        } catch (error) {
        }
      }
    
      scrollToIndex = (index) => {
        const layout = this.layoutList[index];
        if (!layout) {
          return;
        }
        if (this.firstTabScrollViewRef1.current && this.firstTabScrollViewRef2.current) {
          //先算出tab的中心点,再算出scrollview实际需要滑动的距离(scrollview左边的left位置-屏幕中间位置=实际超出屏幕的位置)
          const left = layout.x + (layout.width) / 2;
          const x = left - (ScreenUtils.width / 2);
          this.firstTabScrollViewRef1.current.scrollTo({ x: x, animated: true });
          this.firstTabScrollViewRef2.current.scrollTo({ x: x, animated: true });
        }
      }
    
      render() {
        const { refreshing, dataList, loadEnd, isLoadMore, headerHeight } = this.state;
        return <View style={styles.container}>
          <Animated.FlatList
            style={{ flex: 1 }}
            contentContainerStyle={{
              paddingBottom: ScreenUtils.safeBottom,
              backgroundColor: '#fff',
            }}
            showsVerticalScrollIndicator={false}
            data={dataList}
            ListHeaderComponent={this.renderHeader}
            renderItem={this.renderItem}
            onRefresh={this.onRefresh}
            onEndReached={this.onEndReached}
            refreshing={refreshing}
            keyExtractor={(__, k) => `${k}`}
            ListEmptyComponent={loadEnd && <Footer noType={"no_hot_product"} />}
            ListFooterComponent={isLoadMore && <ListFooter />}
            onScroll={Animated.event([
              {
                nativeEvent: { contentOffset: { y: this.scrollY } }
              }], { useNativeDriver: false })
            } />
    
          <Animated.View
            style={{
              position: 'absolute',
              top: 0,
              opacity: this.scrollY.interpolate({
                inputRange: [-1, 0, 60],
                outputRange: [0, 0, 1],
              })
            }}>
            <NavBar backgroundColor={"#fff"} hideBottomLine={true}>{"标题"}</NavBar>
            {headerHeight > 0 && <Animated.View
              style={{
                width: ScreenUtils.width,
                position: 'absolute',
                backgroundColor: '#fff',
                opacity: this.scrollY.interpolate({
                  inputRange: [0, headerHeight - 90 - ScreenUtils.headerHeight, headerHeight - 90 - ScreenUtils.headerHeight + 1],
                  outputRange: [0, 0, 1],
                }),
                top: this.scrollY.interpolate({
                  inputRange: [0, headerHeight - 90 - ScreenUtils.headerHeight, headerHeight - 90 - ScreenUtils.headerHeight + 1],
                  outputRange: [-120, ScreenUtils.headerHeight, ScreenUtils.headerHeight],
                })
              }}>
              {this.renderTabView(this.firstTabScrollViewRef2, { marginTop: 0 })}
            </Animated.View>}
          </Animated.View>
    
          {this.renderFloatModal()}
        </View>
      }
    
      //类目悬浮窗
      renderFloatModal = () => {
        const { firstTabList, firstTabIndex, showFloatModal, headerHeight } = this.state;
        if (!showFloatModal) {
          return null;
        }
        return <Animated.View
          style={{
            flex: 1,
            position: 'absolute',
            top: this.scrollY.interpolate({
              inputRange: [0, headerHeight - 90 - ScreenUtils.headerHeight, headerHeight - 90 - ScreenUtils.headerHeight + 1],
              outputRange: [headerHeight - 50, ScreenUtils.headerHeight + 40, ScreenUtils.headerHeight + 40],
            }),
            left: 0,
            right: 0,
            bottom: 0,
          }}>
          <ScrollView style={{
            height: ScreenUtils.height / 3,
            maxHeight: ScreenUtils.height / 3,
            backgroundColor: '#fff'
          }}>
            <View style={styles.floatContainer}>
              {firstTabList.map((item, index) => {
                const isSelected = (firstTabIndex == index);
                return <UIButtonWithSingleText key={index}
                  btnStyle={{
                    backgroundColor: isSelected ? '#FFE7EC' : '#F7F8F9',
                    borderRadius: 20,
                    paddingHorizontal: 13,
                    paddingVertical: 6,
                    marginBottom: 12,
                    marginRight: 16
                  }}
                  text={item.name}
                  textStyle={{ color: isSelected ? '#FA0C43' : '#666', fontSize: 12 }}
                  onPress={() => {
                    this.scrollToIndex(index);
                    this.setState({
                      firstTabIndex: index,
                      showFloatModal: false
                    });
    
                  }
                  } />
              })}
            </View>
          </ScrollView>
          <UIButton style={{ flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.30)' }}
            onPress={() => this.setState({ showFloatModal: false })} />
        </Animated.View>
      }
    
      renderHeader = () => {
        return <View
          onLayout={(event) => {
            this.setState({ headerHeight: (event.nativeEvent?.layout?.height ?? 0) })
          }}>
          <ImageBackground source={require("@assets/home/homePage/bset_follow_bg.png")}
            style={{
              width: ScreenUtils.width,
              height: ScreenUtils.width / 1.6
            }}>
            <NavBar whiteBack={true}
              backgroundColor={"transparent"}
              hideBottomLine={true}>{"标题"}</NavBar>
            <View style={{ flex: 1 }}>
              <Text style={styles.title}>爆品榜单</Text>
              <Text style={styles.otherTitle}>副文案副文案副文案</Text>
            </View>
          </ImageBackground>
          {this.renderTabView(this.firstTabScrollViewRef1)}
        </View>
      }
    
      renderTabView = (ref, style) => {
        const { firstTabList, firstTabIndex, secondTabList, secondTabIndex, showFloatModal } = this.state;
        return <View style={[{ marginTop: -12 }, style]}>
          <View style={styles.tabContainer}>
            <ScrollView
              ref={ref}
              horizontal={true}
              showsHorizontalScrollIndicator={false}
              style={styles.firstTabContainer}>
              {firstTabList.map((item, index) => {
                const isSelected = (firstTabIndex == index);
                return <TouchableOpacity style={styles.tabItemView} key={index}
                  onPress={() => this.setState({ firstTabIndex: index })}
                  onLayout={(event) => {
                    this.layoutList[index] = event.nativeEvent.layout;
                  }}>
                  <Text style={{ color: isSelected ? '#000' : '#B3B3B3', fontSize: 14 }}>{item.name}</Text>
                  {isSelected && <View style={styles.tabItemLine} />}
                </TouchableOpacity>
              })}
            </ScrollView>
    
            <UIButtonWithImage
              source={require("@assets/meetplace/more_icon.png")}
              imageStyle={{
                width: 16,
                height: 15,
                marginHorizontal: 13,
                transform: [{ rotate: showFloatModal ? '-90deg' : '0deg' }]
              }}
              btnStyle={{ height: 40, width: 42 }}
              onPress={() => {
                const show = !showFloatModal;
                this.setState({ showFloatModal: show })
              }} />
          </View>
          <View style={{ flexDirection: 'row', marginLeft: 16, marginVertical: 12 }}>
            {secondTabList.map((item, index) => {
              const isSelected = (secondTabIndex == index);
              return <UIButton key={index}
                style={[styles.tabSecondItemView, { borderColor: isSelected ? '#FA0C43' : '#CDCDCD' }]}
                onPress={() => {
                  this.setState({ secondTabIndex: index }, () => {
                    this.onRefresh(false);
                  })
                }}>
                <Text style={{ color: isSelected ? '#FA0C43' : '#000', fontSize: 12 }}>{item}</Text>
              </UIButton>
            })}
          </View>
        </View>
      }
    
      renderItem = ({ item, index }) => {
        return <ListItemView item={item}
          style={{ marginTop: index == 0 ? 0 : 12 }}
          navigation={this.props.navigation} />
      }
    
      getData() {
        const { secondTabIndex } = this.state;
        //类型 0:总榜 1:日榜 2:周榜 3:月榜
        switch (secondTabIndex) {
          case 0:
            this.params.type = 1;
            break;
          case 1:
            this.params.type = 2;
            break;
          case 2:
            this.params.type = 0;
            break;
        }
        api_goods_hot_rank(this.params).then((res) => {
          this.setState({ refreshing: false, loadEnd: true, isLoadMore: false });
          const rows = res.list;
          this.hasMore = this.params.page < res.totalPage;
          let newData = rows;
          if (this.params.page > 1) {
            newData = this.state.dataList.concat(rows);
          }
          this.setState({ dataList: newData });
        }).catch((error) => {
          this.setState({ refreshing: false, loadEnd: true, isLoadMore: false });
        })
      }
    
      onRefresh = (refreshing = true) => {
        this.params.page = 1;
        this.setState({ refreshing }, () => {
          this.getData()
        })
      }
    
      onEndReached = () => {
        if (this.hasMore) {
          this.params.page += 1;
          this.setState({ isLoadMore: true })
          this.getData();
        }
      }
    
    }
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        backgroundColor: "#F9F9F9",
      },
      title: {
        color: '#fff',
        fontSize: 28,
        fontWeight: '800',
        marginTop: 20,
        marginLeft: 20
      },
      otherTitle: {
        color: '#fff',
        fontSize: 14,
        fontWeight: '600',
        marginLeft: 20,
        marginTop: 4
      },
      tabContainer: {
        borderTopLeftRadius: 12,
        borderTopRightRadius: 12,
        backgroundColor: '#fff',
        height: 40,
        flexDirection: 'row',
        alignItems: 'center',
        borderBottomColor: '#ECECEC',
        borderBottomWidth: 1
      },
      firstTabContainer: {
        flexDirection: 'row',
        flex: 1,
        height: 40,
        maxHeight: 40,
        paddingHorizontal: 4,
      },
      tabItemView: {
        paddingHorizontal: 12,
        alignItems: 'center',
        justifyContent: 'center',
        height: 40
      },
      tabItemLine: {
        backgroundColor: '#000',
        height: 2,
        borderRadius: 6,
        width: 32,
        position: 'absolute',
        bottom: 0
      },
      floatContainer: {
        flexDirection: 'row',
        flexWrap: 'wrap',
        backgroundColor: '#fff',
        paddingLeft: 16,
        paddingTop: 16,
        paddingBottom: 4
      }
    });
    

    相关文章

      网友评论

          本文标题:react-native页面滑动时添加顶部悬浮Navbar、添加

          本文链接:https://www.haomeiwen.com/subject/wuoigdtx.html