首页IT科技android瀑布流布局(瀑布流使用虚拟列表性能优化)

android瀑布流布局(瀑布流使用虚拟列表性能优化)

时间2025-09-17 19:38:24分类IT科技浏览6249
导读:瀑布流算是比较常见的布局了,一个般常见纵向瀑布流的交互,当我们滚动到底的时候加载下一页的数据追加到上去。因为一次加载的数据量不是很多,页面操作是也不会有太大的性能消耗。但是如果当你一直往下滚动加载,加载几十页的时候,就会开始感觉不那么流畅的,这是因为虽然每次操作的很少,但是页面的 DOM 越来越多,内存占用也会增大,而且发生重排重...

瀑布流算是比较常见的布局了                 ,一个般常见纵向瀑布流的交互                           ,当我们滚动到底的时候加载下一页的数据追加到上去                  。因为一次加载的数据量不是很多         ,页面操作是也不会有太大的性能消耗                          。但是如果当你一直往下滚动加载        ,加载几十页的时候                           ,就会开始感觉不那么流畅的                  ,这是因为虽然每次操作的很少        ,但是页面的 DOM 越来越多                          ,内存占用也会增大                  ,而且发生重排重绘时候浏览器计算量耗时也会增大,就导致了慢慢不能那么流畅了         。这个时候可以选择结合虚拟列表方式使用                          ,虚拟列表本身就是用来解决超长列表时的处理方案                  。

瀑布流

瀑布流的实现方式有很多种                           ,大体分为:

CSS: CSS 实现的有 multi-column                 、grid ,CSS 实现存在一定局限性                 ,例如无法调整顺序                           ,当元素高度差异较大时候不是很好处理各列间隔差等                          。 JavaScript:JavaScript 实现的有 JavaScript + flex                           、JavaScript + position         ,JavaScript 实现兼容性较好                 ,可控制性高         。

因为我的瀑布流是可提前计算元素宽高                           ,列数是动态的         ,所以采用了 JavaScript + position 来配合 虚拟列表 进行优化         。

js + flex 实现

如果你的瀑布流 列是固定        ,列宽不固定 的                           ,使用 flex 是个很好选择                  ,当你的容器宽度变话时候        ,每一列宽度会自适应                          ,大致实现方式

将你的数据分为对应列数

let data1 = [], //第一列 data2 = [], //第二列 data3 = [], //第三列 i = 0; while (i < data.length) { data1.push(data[i++]); if (i < data.length) { data2.push(data[i++]); } if (i < data.length) { data3.push(data[i++]); } }

然后将你的每列数据插入进去就可以了                  ,设置 list 为 flex 容器,并设置主轴方向为 row

<div class="list"> <!-- 第一列 --> <div class="column"> <div class="item"></div> <!-- more items--> </div> <!-- 第二列 --> <div class="column"> <div class="item"></div> <!-- more items--> </div> <!-- 第三列 --> <div class="column"> <div class="item"></div> <!-- more items--> </div> </div>

js + position 实现

这种方式比较适合 列定宽                          ,列数量不固定情况                           ,而且最好能计算出每个元素的大小                          。

大致 HTML 结构如下:

<ul class="list"> <li class="list-item"></li> <!-- more items--> </ui> <style> .list { position: relative; } .list-item { position: absolute; top: 0; left: 0; } </style>

JavaScript 部分,首先需要获取 list 宽度                 ,根据 list.width/列宽 计算出列的数量                           ,然后根据列数量去分组数据和计算位置

// 以列宽为300 间隔为20 为例 let catchColumn = (Math.max(parseInt((dom.clientWidth + 20) / (300 + 20)), 1)) const toTwoDimensionalArray = (count) => { let list = [] for (let index = 0; index < count; index++) { list.push([]) } return list; } const minValIndex = (arr = []) => { let val = Math.min(...arr); return arr.findIndex(i => i === val) } // 缓存累计高度 let sumHeight = toTwoDimensionalArray(catchColumn) data.forEach(item => { // 获取累计高度最小那列 const minIndex = minValIndex(sumHeight) let width = 0 // 这里宽高更具需求计算出来 let height = 0 item._top = minIndex * (300 + 20) // 缓存位置信息         ,后面会用到 item.style = { width: width + px, height: height + px, // 计算偏移位置 transform: `translate(${minIndex * (300 + 20)}px, ${sumHeight[minIndex]}px)` } sumHeight[minIndex] = sumHeight[minIndex] + height + 20 })

动态列数

可以使用 ResizeObserver(现代浏览器兼容比较好了) 监听容器元素大小变化                 ,当宽度变化时重新计算列数量                           ,当列数量发生变化时重新计算每项的位置信息                 。

const observer = debounce((e) => { const column = updateVisibleContainerInfo(visibleContainer) if (column !== catchColumn) { catchColumn = column // 重新计算 this.resetLayout() } }, 300) const resizeObserver = new ResizeObserver(e => observer(e)); // 开始监听 resizeObserver.observe(dom);

过渡动画

当列数量发生变化时候         ,元素项的位置很多都会发生变化        ,如下图                           ,第 4 项的位置从第 3 列变到了第 4 项                  ,如果不做处理会显得比较僵硬         。

1 2 3 4

4

好在我们使用了 transform(也是为什么不使用 top         、left 原因        ,transform 动画性能更高) 进行位置偏移                          ,可以直接使用 transition 过渡                           。

.list-item { position: absolute; top: 0; left: 0; transition: transform .5s ease-in-out; }

使用虚拟列表

瀑布流存在的问题

很多虚拟列表的都是使用的单列定高使用方式                  ,但是瀑布流使用虚拟列表方式有点不同,瀑布流存在多列且时是错位的                 。所以常规 length*height 为列表总高度                          ,根据 scrollTop/height 来确定下标方式就行不通了                           ,这个时候高度需要根据瀑布流高度动态决定了,可显示元素也不能通过 starindex-endindex 去截取显示了。

如下图:蓝色框的元素是不应该显示的                 ,只有与可视区域存在交叉的元素才应该显示

1 2 3 4 5 6

8

9

10

7

12

11

13

15 14

可视元素判定

先来看下面图                           ,当元素完全不在可视区域时候就视为当前元素不需要显示         ,只有与可视区域存在交叉或被包含时候视为需要显示                           。

1 2 3 4 5 6

8

9

10

7

12

11

13 14

已 滚 动 高 度 可 视 区 高 度

瀑 布 流 总 高 度

因为上面瀑布流的实现采用的是 position 定位的                 ,所以我们完全能知道所有元素距离顶部的距离                           ,很容易计算出与可视区域交叉位置                          。

元素偏移位置 < 滚动高度+可视区域高度 && 元素偏移位置 + 元素高度 > 滚动高度

如果只渲染可视区域范围         ,滚动时候会存在白屏再出现        ,可视适当的扩大渲染区域                           ,例如把上一屏和下一屏都算进来                  ,进行预先渲染。

const top = scrollTop - clientHeight const bottom = scrollTop + clientHeight * 2 const visibleList = data.filter(item => item._top + item.height > top && item._top < bottom)

然后通过监听滚动事件        ,根据滚动位置去处理筛选数                  。这里会存在一个隐藏性能问题                          ,当滚动加载数据比较多的时候                  ,滚动事件触发也是比较快的,每一次都进行一次遍历                          ,也是比较消耗性能的                          。可以适当控制一下事件触发频率                           ,当然这也只是治标不治本,归根倒是查询显示元素方法问题         。

标记下标

应为列表数据的 _top 值是从小到大正序的                 ,所以我们可以标记在可视区元素的下标                           ,当发生滚动的时候         ,我们直接从标记下标开始查找                 ,根据滚动分几种情况来判断                  。

1> 如果滚动后                           ,标记下标元素还在可视范围内         ,可以直接从标记下标二分查找        ,往上往下找直到不符合条件就停止                          。

2> 如果滚动后                           ,标记下标元素不在可视范围内                  ,根据滚动方向往上或者往下去查找         。这个时候存在两种情况        ,一种是滚动幅度比较小                          ,直接根据当前下标往上或者往下找         。当用户拖动滚动条滚动幅度特别大的时候                  ,可以将下标往上或者往下偏移,偏移量根据 滚动高度/预估平均高度*列数 去估算一个                          ,然后在根据这个预估下标进行查找                          。找到后然后缓存一个新的下标                 。

抖动问题

我们 absolute 定位会撑开容器高度                           ,但是滚动时候还是会存在抖动问题,我们可以自定义一个元素高度去撑开                 ,这个元素高度也就是我们之前计算的每一列累计高度 sumHeight 中最大的那个了         。

过渡动画问题

当列宽发生变化时                           ,元素位置发生了变化         ,在可视区域的元素也发生了变化                 ,有些元素可能之前并没有渲染                           ,所以使用上面 CSS 会存在新出现元素不会产生过渡动画                           。好在我们能够很清楚的知道元素原位置信息和新的位置信息         ,我们可以利用 FLIP 来处理这动画        ,很容易控制元素过渡变化                           ,如果有些元素之前不存在                  ,就没有原位置信息        ,我们可以在可视范围内给他随机生成一个位置进行过渡                          ,保证每一个元素都有个过渡效果避免僵硬                 。

总结

上面情况仅仅是针对动态列数量                  ,又能计算出高度情况下优化,可能业务中也是可能存在每项高度是动态的                          ,这个时候可以采用预估元素高度在渲染后缓存大小位置等信息                           ,或者离屏渲染等方案解决做出进一步的优化处理。

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
bios没有usb wake up(BIOS里没有USB-HDD选项具体开启步骤) wordpress文章形式(为你的WordPress网站增添魅力——五款强大的文章插件)