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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM