热门标签 | HotTags
当前位置:  开发笔记 > 前端 > 正文

在微信小程序中实现virtuallist的方法详解

本文是介绍在微信小程序中实现virtual-list的方法详解。

1.列表项的宽/高和滚动偏移量

在虚拟列表中,依赖每一个列表项的宽/高来计算「可滚动区域」,而且可能是需要自定义的,定义itemSizeGetter函数来计算列表项宽/高。

itemSizeGetter(itemSize) {      return (index: number) => {        if (isFunction(itemSize)) {          return itemSize(index);
        }        return isArray(itemSize) ? itemSize[index] : itemSize;
      };
    }

滚动过程中,不会计算没有出现过的列表项的itemSize,这个时候会使用一个预估的列表项estimatedItemSize,目的就是在计算「可滚动区域」高度的时候,没有测量过的itemSize用estimatedItemSize代替。

getSizeAndPositionOfLastMeasuredItem() {    return this.lastMeasuredIndex >= 0
      ? this.itemSizeAndPositionData[this.lastMeasuredIndex]
      : { offset: 0, size: 0 };
  }

getTotalSize(): number {    const lastMeasuredSizeAndPosition = this.getSizeAndPositionOfLastMeasuredItem();    return (
      lastMeasuredSizeAndPosition.offset +
      lastMeasuredSizeAndPosition.size +
      (this.itemCount - this.lastMeasuredIndex - 1) * this.estimatedItemSize
    );
  }

这里看到了是直接通过缓存命中最近一个计算过的列表项的itemSize和offset,这是因为在获取每一个列表项的两个参数时候,都对其做了缓存。

 getSizeAndPositionForIndex(index: number) {    if (index > this.lastMeasuredIndex) {      const lastMeasuredSizeAndPosition = this.getSizeAndPositionOfLastMeasuredItem();      let offset =
        lastMeasuredSizeAndPosition.offset + lastMeasuredSizeAndPosition.size;      for (let i = this.lastMeasuredIndex + 1; i <= index; i++) {        const size = this.itemSizeGetter(i);        this.itemSizeAndPositionData[i] = {
          offset,
          size,
        };

        offset += size;
      }      this.lastMeasuredIndex = index;
    }    return this.itemSizeAndPositionData[index];
 }

2.根据偏移量搜索索引值

在滚动过程中,需要通过滚动偏移量offset计算出展示在「可视区域」首项数据的索引值,一般情况下可以从0开始计算每一列表项的itemSize,累加到一旦超过offset,就可以得到这个索引值。但是在数据量太大和频繁触发的滚动事件中,会有较大的性能损耗。好在列表项的滚动距离是完全升序排列的,所以可以对已经缓存的数据做二分查找,把时间复杂度降低到 O(lgN) 。

js代码如下:

  findNearestItem(offset: number) {
    offset = Math.max(0, offset);    const lastMeasuredSizeAndPosition = this.getSizeAndPositionOfLastMeasuredItem();    const lastMeasuredIndex = Math.max(0, this.lastMeasuredIndex);    if (lastMeasuredSizeAndPosition.offset >= offset) {      return this.binarySearch({        high: lastMeasuredIndex,        low: 0,
        offset,
      });
    } else {      return this.exponentialSearch({        index: lastMeasuredIndex,
        offset,
      });
    }
  }

 private binarySearch({
    low,
    high,
    offset,
  }: {    low: number;
    high: number;
    offset: number;
  }) {    let middle = 0;    let currentOffset = 0;    while (low <= high) {
      middle = low + Math.floor((high - low) / 2);
      currentOffset = this.getSizeAndPositionForIndex(middle).offset;      if (currentOffset === offset) {        return middle;
      } else if (currentOffset  offset) {
        high = middle - 1;
      }
    }    if (low > 0) {      return low - 1;
    }    return 0;
  }

对于搜索没有缓存计算结果的查找,先使用指数查找缩小查找范围,再使用二分查找。

private exponentialSearch({
    index,
    offset,
  }: {    index: number;
    offset: number;
  }) {    let interval = 1;    while (
      index 

3.计算startIndex、endIndex

我们知道了「可视区域」尺寸containerSize,滚动偏移量offset,在加上预渲染的条数overscanCount进行调整,就可以计算出「可视区域」起始项的索引值startIndex和结束项索引值endIndex,实现步骤如下:

1.找到距离offset最近的索引值,这个值就是起始项的索引值startIndex;

2.通过startIndex获取此项的offset和size,再对offset进行调整;

3.offset加上containerSize得到结束项的maxOffset,从startIndex开始累加,直到越过maxOffset,得到结束项索引值endIndex。

js代码如下:

 getVisibleRange({
    containerSize,
    offset,
    overscanCount,
  }: {    containerSize: number;
    offset: number;
    overscanCount: number;
  }): { start?: number; stop?: number } {    const maxOffset = offset + containerSize;    let start = this.findNearestItem(offset);    const datum = this.getSizeAndPositionForIndex(start);
    offset = datum.offset + datum.size;    let stop = start;    while (offset 

3.监听scroll事件,实现虚拟列表滚动

现在可以通过监听scroll事件,动态更新startIndex、endIndex、totalSize、offset,就可以实现虚拟列表滚动。

js代码如下:

  getItemStyle(index) {      const style = this.styleCache[index];      if (style) {        return style;
      }      const { scrollDirection } = this.data;      const {
        size,
        offset,
      } = this.sizeAndPositionManager.getSizeAndPositionForIndex(index);      const cumputedStyle = styleToCssString({        position: &#39;absolute&#39;,        top: 0,        left: 0,        width: &#39;100%&#39;,
        [positionProp[scrollDirection]]: offset,
        [sizeProp[scrollDirection]]: size,
      });      this.styleCache[index] = cumputedStyle;      return cumputedStyle;
  },
  
  observeScroll(offset: number) {      const { scrollDirection, overscanCount, visibleRange } = this.data;      const { start, stop } = this.sizeAndPositionManager.getVisibleRange({        containerSize: this.data[sizeProp[scrollDirection]] || 0,
        offset,
        overscanCount,
      });      const totalSize = this.sizeAndPositionManager.getTotalSize();      if (totalSize !== this.data.totalSize) {        this.setData({ totalSize });
      }      if (visibleRange.start !== start || visibleRange.stop !== stop) {        const styleItems: string[] = [];        if (isNumber(start) && isNumber(stop)) {          let index = start - 1;          while (++index <= stop) {
            styleItems.push(this.getItemStyle(index));
          }
        }        this.triggerEvent(&#39;render&#39;, {          startIndex: start,          stopIndex: stop,
          styleItems,
        });
      }      this.data.offset = offset;      this.data.visibleRange.start = start;      this.data.visibleRange.stop = stop;
  },

在调用的时候,通过render事件回调出来的startIndex, stopIndex,styleItems,截取长列表「可视区域」的数据,在把列表项目的itemSize和offset通过绝对定位的方式应用在列表上

代码如下:

let list = Array.from({ length: 10000 }).map((_, index) => index);

Page({  data: {    itemSize: index => 50 * ((index % 3) + 1),    styleItems: null,    itemCount: list.length,    list: [],
  },
  onReady() {    this.virtualListRef =      this.virtualListRef || this.selectComponent(&#39;#virtual-list&#39;);
  },

  slice(e) {    const { startIndex, stopIndex, styleItems } = e.detail;    this.setData({      list: list.slice(startIndex, stopIndex + 1),
      styleItems,
    });
  },

  loadMore() {
    setTimeout(() => {      const appendList = Array.from({ length: 10 }).map(        (_, index) => list.length + index,
      );
      list = list.concat(appendList);      this.setData({        itemCount: list.length,        list: this.data.list.concat(appendList),
      });
    }, 500);
  },
});

  
    
      {{ item + 1 }}
    
  
  {{itemCount}}

以上就是在微信小程序中实现virtual-list的方法详解的详细内容,更多请关注其它相关文章!


推荐阅读
  • 程序员如何优雅应对35岁职业转型?这里有深度解析
    本文探讨了程序员在职业生涯中如何通过不断学习和技能提升,优雅地应对35岁左右的职业转型挑战。我们将深入分析当前热门技术趋势,并提供实用的学习路径。 ... [详细]
  • 国内BI工具迎战国际巨头Tableau,稳步崛起
    尽管商业智能(BI)工具在中国的普及程度尚不及国际市场,但近年来,随着本土企业的持续创新和市场推广,国内主流BI工具正逐渐崭露头角。面对国际品牌如Tableau的强大竞争,国内BI工具通过不断优化产品和技术,赢得了越来越多用户的认可。 ... [详细]
  • 本文将深入探讨PHP编程语言的基本概念,并解释PHP概念股的含义。通过详细解析,帮助读者理解PHP在Web开发和股票市场中的重要性。 ... [详细]
  • 嵌入式开发环境搭建与文件传输指南
    本文详细介绍了如何为嵌入式应用开发搭建必要的软硬件环境,并提供了通过串口和网线两种方式将文件传输到开发板的具体步骤。适合Linux开发初学者参考。 ... [详细]
  • 微信小程序:授权登录与手机号绑定
    本文详细介绍了微信小程序中用户授权登录及绑定手机号的流程,结合官方指引和实际开发经验,提供了一套完整的实现方案,帮助开发者更好地理解和应用。 ... [详细]
  • 使用JS、HTML5和C3创建自定义弹出窗口
    本文介绍如何结合JavaScript、HTML5和C3.js来实现一个功能丰富的自定义弹出窗口。通过具体的代码示例,详细讲解了实现过程中的关键步骤和技术要点。 ... [详细]
  • 深入理解小程序中的Picker组件
    Picker组件是一种从屏幕底部弹出的滚动选择器,支持多种选择模式,包括普通选择器、多列选择器、时间选择器、日期选择器和省市区选择器。本文将详细介绍Picker的各种属性及其应用场景。 ... [详细]
  • 微信小程序中实现位置获取的全面指南
    本文详细介绍了如何在微信小程序中实现地理位置的获取,包括通过微信官方API和腾讯地图API两种方式。文中不仅涵盖了必要的准备工作,如申请开发者密钥、下载并配置SDK等,还提供了处理用户授权及位置信息获取的具体代码示例。 ... [详细]
  • 本文探讨了在C语言编程中,如何有效避免多文件项目中的重定义问题,通过合理使用预处理器指令和extern关键字,确保代码的健壮性和可维护性。 ... [详细]
  • 区块链的兴起:恰逢其时,犹如1996年的互联网
    本文探讨了区块链技术的发展阶段,将其与1996年互联网的兴起进行对比,分析了当前区块链技术的现状及其未来潜力。 ... [详细]
  • 掌握Spring MVC中自定义类型转换与格式化的技巧
    近期,在开发一款小程序的过程中遇到了几个Spring MVC接口需要传递时间参数的问题。本文将详细介绍如何利用Java 8 Time API在Spring MVC中实现时间参数的自定义类型转换和格式化。 ... [详细]
  • 探讨在微信小程序环境中是否能够获取到全局this对象,并分析其背后的技术实现。 ... [详细]
  • 本文作者分享了在阿里巴巴获得实习offer的经历,包括五轮面试的详细内容和经验总结。其中四轮为技术面试,一轮为HR面试,涵盖了大量的Java技术和项目实践经验。 ... [详细]
  • 如何在电脑上同时登录多个微信账号?实用技巧全解析
    本文详细介绍了如何在电脑上同时登录多个微信账号的方法,并分享了一些微信的隐藏小技巧,帮助用户更高效地使用微信。 ... [详细]
  • 深入解析Java枚举及其高级特性
    本文详细介绍了Java枚举的概念、语法、使用规则和应用场景,并探讨了其在实际编程中的高级应用。所有相关内容已收录于GitHub仓库[JavaLearningmanual](https://github.com/Ziphtracks/JavaLearningmanual),欢迎Star并持续关注。 ... [详细]
author-avatar
传导网络
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有