zoukankan      html  css  js  c++  java
  • Taro实现VirtualList虚拟列表

            在使用Taro开发微信小程序时,需要加载长列表数据,在官网找了相关的VirtualList虚拟列表的组件,要么版本过低(项目中使用3.0.1版本),要么使用不方便(可能是自己没看懂的问题),官方也说有虚拟列表就是长列表加载,使用后发现性能不能达到满足,于是就参考网上的虚拟列表的思路开始自己做。

          简单说下思路,设计思路是通过虚拟列表,只展示屏幕可视区域范围的数据,但在taro中效果还是有点卡顿,经大佬指点,渲染可视区域时,
    渲染组件不再进行删除和新建,而是创建指定数量的组件展示,向下滚动时,将最上面的组件通过css修改样式显示的位置,
    形成一种链条滚动的形式。

         简化的思路图:

          

    下面贴出主要的代码:

      1 /**
      2  * author: wang.p  2021-09-18
      3  *
      4  * description:  自定义虚拟列表
      5  *
      6  * */
      7 
      8 import React, {Component} from "react"
      9 import {ScrollView, View} from '@tarojs/components'
     10 import PropsType from 'prop-types';
     11 import classnames from 'classnames';
     12 import './virtual-list.scss';
     13 
     14 class VirtualList extends Component {
     15 
     16   static propTypes = {
     17     className: PropsType.string, // 样式名
     18     rowCount: PropsType.number,  // 渲染的行数
     19     source: PropsType.array,  // 数据源数组
     20     rowHeight: PropsType.number,  // 行高
     21     scrollToIndex: PropsType.number, // 跳转到指定的位置
     22     getRowHeight: PropsType.func,  // 动态行高
     23     onScroll: PropsType.func, // 滚动处罚事件
     24     onRowRender: PropsType.func, // 行渲染
     25     onSrollTopRecommend: PropsType.func, // 触发顶部样式事件   这个是本人项目中使用的,可以不用
     26   }
     27 
     28   static defaultProps = {
     29     rowCount: 20,
     30     source: [],
     31     rowHeight: 40
     32   }
     33 
     34   state = {
     35     rowCount: 20, // 显示行数
     36     scrollHeight: 0, // 所有内容渲染的高度
     37     scrollData: [],  // 渲染可视区域数据的数组
     38     scrollStyles: [], // 样式数组
     39     isCategoryToScroll: false, // 是否是分类切换定位滚动
     40     scrollToIndex: 0, // 跳转到指定位置
     41     compareHeight: 0  // 触发渲染的高度差
     42   }
     43 
     44   componentWillMount = () => {
     45     const {rowCount, source, rowHeight, getRowHeight} = this.props;
     46     let scrollStyles = [];
     47     let scrollData = [];
     48     let scrollHeight = 0;
     49     let compareHeight = 0;
     50     source.forEach((item, idx) => {
     51       let styles = {position: 'absolute', left: 0, top: scrollHeight};
     52       scrollStyles.push(styles);
     53       let tempHeight = typeof getRowHeight === 'function' ? getRowHeight(idx, item) : rowHeight;
     54       scrollHeight += tempHeight;
     55     });
     56 
     57     let showCount = source.length < rowCount ? source.length : rowCount;
     58     for (let i = 0; i < showCount; i++) {
     59       scrollData.push({sort: i, row: i})
     60     }
     61 
     62     compareHeight = Math.floor(scrollStyles[showCount - 1].top / showCount) * 3;
     63 
     64     this.setState({scrollHeight, scrollData, scrollStyles, rowCount, compareHeight});
     65   }
     66 
     67 
     68   componentDidMount = () => {
     69 
     70   }
     71 
     72   componentWillReceiveProps(nextProps: Readonly<P>, nextContext: any) {
     73 
     74     if (nextProps.scrollToIndex != this.state.scrollToIndex && this.state.scrollStyles.length > 0) {
     75       let scrollToIndex = nextProps.scrollToIndex > this.state.scrollStyles.length ? this.state.scrollStyles.length - 1 : nextProps.scrollToIndex;
     76       let scrollTop = this.state.scrollStyles[scrollToIndex];
     77       if (scrollTop) {
     78         this.setState({scrollToIndex: nextProps.scrollToIndex, scrollTop: scrollTop.top , isCategoryToScroll: true});
     79       }
     80     }
     81   }
     82 
     83   render() {
     84     const {className, style } = this.props;
     85     const {scrollHeight, scrollData, scrollStyles, scrollTop} = this.state;
     86 
     87     return <ScrollView className={classnames('self-virtual-list', className)}
     88                        style={{...style}}
     89                        scrollTop={scrollTop}
     90                        scrollY={true}
     91                        scrollWithAnimation
     92                        onScroll={this.onScroll.bind(this)}>
     93       <View className={'self-virtual-list-body'} style={{height: scrollHeight}}>
     94         {scrollData.length > 0 && scrollData.map((item, idx) => {
     95           return this.props.onRowRender(item, scrollStyles[item.row]);
     96         })}
     97       </View>
     98     </ScrollView>
     99 
    100 
    101   }
    102 
    103   currentScrollTop = 0;
    104   prevScrollTop = 0;  // 记录上次滚动的Scrolltop
    105 
    106   findMinOrMax = (data, isMax= false) => {
    107     if (isMax) {
    108       return data.reduce((prev, next) => {
    109         if (prev.row < next.row) {
    110           return next;
    111         } else {
    112           return prev;
    113         }
    114       })
    115     } else {
    116       return data.reduce((prev, next) => {
    117         if (prev.row < next.row) {
    118           return prev;
    119         } else {
    120           return next;
    121         }
    122       })
    123     }
    124   }
    125 
    126   /**
    127    * 滚动事件,计算渲染菜品的数据
    128    * 滚动到顶部时,如果顶部有推荐菜品就展示出来,如果上拉滚动,就隐藏推荐菜品
    129    * */
    130   onScroll = (event) => {
    131     let scrollY = event.detail.deltaY;
    132     const eventScrollTop = event ? event.detail.scrollTop : this.state.scrollTop;
    133     const {scrollStyles, rowCount, compareHeight, scrollData, isCategoryToScroll} = this.state;
    134 
    135     if (Math.abs(this.currentScrollTop - eventScrollTop) > compareHeight || eventScrollTop <= scrollStyles[3].top || eventScrollTop >= scrollStyles[scrollStyles.length - 5].top) {
    136       // 查询出当前scrollTop在那个范围
    137       this.currentScrollTop = eventScrollTop;
    138       let scrollIndex = 0;
    139       for(let i=1;i<scrollStyles.length;i++) {
    140         if (scrollStyles[i - 1].top <= eventScrollTop && scrollStyles[i].top > eventScrollTop) {
    141           scrollIndex = i;
    142           break;
    143         }
    144       }
    145       // 计算出渲染范围的最小下标和最大下标
    146       let minIndex = scrollIndex - parseInt(Math.floor(rowCount / 2.0));
    147       if (minIndex < 0) {
    148         minIndex = 0;
    149       }
    150       let maxIndex = minIndex + rowCount;
    151       if (maxIndex > scrollStyles.length - 1) {
    152         maxIndex = scrollStyles.length - 1;
    153       }
    154       // 找出当前显示的数据范围最小值和最大值
    155       let minData = this.findMinOrMax(scrollData);
    156       let maxData = this.findMinOrMax(scrollData, true);
    157       let newScrollData = [...scrollData];
    158       if (minIndex > minData.row) {
    159         // 向下滑动渲染, 找出最小值,替换成最大值,循环进行替换
    160         let cycle = minIndex - minData.row;
    161         for (let i = 0; i < cycle; i++) {
    162           minData = this.findMinOrMax(scrollData);
    163           maxData = this.findMinOrMax(scrollData, true);
    164 
    165           scrollData[minData.sort]['row'] = maxData.row + 1;
    166         }
    167 
    168         this.setState({scrollData: newScrollData, isCategoryToScroll: false});
    169       } else {
    170         // 向上滑动渲染
    171         let cycle = minData.row - minIndex;
    172         for (let i = 0; i < cycle; i++) {
    173           minData = this.findMinOrMax(scrollData);
    174           maxData = this.findMinOrMax(scrollData, true);
    175 
    176           scrollData[maxData.sort]['row'] = minData.row - 1;
    177         }
    178         this.setState({scrollData: newScrollData, isCategoryToScroll: false});
    179       }
    180 
    181     }
    182 
    183     let scycelScroll = compareHeight / 3;
    184     // 滚动一定距离,就触发外部事件
    185     if (!isCategoryToScroll && Math.abs(this.prevScrollTop - eventScrollTop) > scycelScroll && this.props.onScroll) {
    186       this.prevScrollTop = eventScrollTop;
    187       let scrollIndex = 0;
    188       for(let i=1;i<scrollStyles.length;i++) {
    189         if (scrollStyles[i - 1].top <= eventScrollTop && scrollStyles[i].top > eventScrollTop) {
    190           scrollIndex = i;
    191           break;
    192         }
    193       }
    194       this.props.onScroll(scrollIndex);
    195     }
    196 
    197     // 处理顶部隐藏的组件
    198     if (this.props.onSrollTopRecommend) {
    199       if (scrollY > 0) {
    200         // 下拉
    201         if (event.detail.scrollTop <= scycelScroll) {
    202           // 展开推荐菜品
    203           this.props.onSrollTopRecommend && this.props.onSrollTopRecommend(true);
    204         }
    205       } else {
    206         // 上拉
    207         if (event.detail.scrollTop > scycelScroll) {
    208           // 触发收起推荐菜品
    209           this.props.onSrollTopRecommend && this.props.onSrollTopRecommend(false);
    210         }
    211       }
    212     }
    213 
    214   }
    215 
    216 
    217 }
    218 
    219 
    220 export default VirtualList

    在项目中的效果图:

       

     使用方法说明:引入组件,添加对应的方法

    <VirtualList className={'category-virtual'}
                width={width}
                height={height}
                source={source}
                rowCount={20}
                getRowHeight={this.getRowHeight}
                scrollToIndex={scrollIndex}
                onRowRender={this.onRowRender}
                onScroll={this.onScroll}
    />
    /**
    * 获取行高
    * @idx 数据源下标
    * @value 数据
    */
    getRowHeight = (idx, value) => {
       // 这里可以通过数据源的下标idx,来返回高度
    
       return 100; // 高度可以通过函数来计算
    }
    /**
    * @data 数据{sort: 渲染的行数的顺序, row: 渲染数据源的下标}
    * @style: 样式
    * */
    onRowRender = (data, style) => {
    const {sort, row} = data;
    const {source} = this.state;
    
    return <View key={sort} style={style}>
       ...
    </View>
    
    }
    /**
    * 通过滚动
    * @currentIndex 分类数组下标
    * */
    onScroll = (currentIndex) => {
    // 通过滚动的触发事件执行某些方法
    
    }

    下面提供代码下载路径:

      链接:https://pan.baidu.com/s/1kW0w1D03N72uCE3S9Vy2zg
      提取码:460i

  • 相关阅读:
    JS计算日期加天数后的日期(起始日期+有效天数=截至日期)
    js取整数、取余数的方法
    js取整数、取余数的方法
    Spring注解@Component、@Repository、@Service、@Controller区别
    Spring注解@Component、@Repository、@Service、@Controller区别
    mybatis参数映射
    PHP实现单点登录最简单的方法
    php cli传递参数的方法
    采集百度搜索的方法
    PHP curl模拟ip和来源进行访问
  • 原文地址:https://www.cnblogs.com/wind-wang/p/15336741.html
Copyright © 2011-2022 走看看