Flutter瀑布流及通用列表解决方案

作者:闲鱼技术-夜澜

背景

目前闲鱼业务中无论是首页还是搜索页都有大量可以落地瀑布流的场景,而在Flutter原生中只提供了ListView, GridView,无法提供自定义布局的能力。

而在社区中,一般瀑布流的解决方案都是基于SliverMultiBoxAdaptor对其performLayout进行定制,主要存在的问题是缺乏复用机制,并且在很多情形下容易出现重复布局,在线上业务的复杂场景下容易出现帧数偏低的问题, 闪屏的问题。同时对于Child生命周期,打点曝光等一系列基础功能的支持还是一片空白的状态。

所以,我们迫切需要一个更为通用的可以解决复杂布局过程同时能够对基础能力进行扩充的列表视图解决方案。

Flutter中的列表视图简介

image.png

1. Scrollable

Scrollable是一个StatefulWidget, 职责是监听用户的手势输入。其State的build方法会返回一个含有Listener和RawGestureDetector的ViewportScrollPosition用于描述其位置信息,并在其内部定义了 onStart, onUpdate, onEnd等回调。Scrollable中的每一次滑动的开始到结束都对应于一个Darg对象,并且会发送滑动的通知。而Viewport则负责对通知进行监听。

2. Sliver

Flutter有两种布局体系 Box, Sliver。在layout的过程中,每个Sliver 都接收 SliverConstraints 计算返回一个 SliverGeometry,可以类比于RenderBox 接收 BoxConstraints 返回一个 Size。Sliver由Viewport统一来负责进行管理。

3. Viewport

A widget that is bigger on the inside.

Viewport持有一个或多个Sliver。Scrollable将offset传递给Viewport, 由Viewport决定哪些Sliver应该是Visible。Viewport本质上是一个MultiChildRenderObjectWidget,也就是整个滚动视图的主要渲染逻辑都在Viewport中完成。

而在performLayout中,_attemptLayout会以center为中心,先布局leading方向的child,再布局trailing方向的child。其中只有dirty的child会被布局。

do {correction = _attemptLayout(mainAxisExtent, crossAxisExtent, offset.pixels + centerOffsetAdjustment);if (correction != 0.0) {offset.correctBy(correction);} else {if (offset.applyContentDimensions(math.min(0.0, _minScrollExtent + mainAxisExtent * anchor),math.max(0.0, _maxScrollExtent - mainAxisExtent * (1.0 - anchor)),))break;}count += 1;
} while (count < _maxLayoutCycles);

如果_attemptLayout返回了一个非0的correction, 就会打断当前布局的过程,需要对offset进行调整后重新开始布局,最多只能连续打断10次(_maxLayoutCycles)。

correction用于调整,举个🌰,比如targetScrollOffset很远,而在scroll的过程中child用完了,就需要让Sliver通知Viewport, 同时进行修正。但是Flutter并不是通过不断对child进行layout来改变child位置实现的滑动效果,这样的重绘过程显然效率太低,显然RenderObject不需要被改变,是可以复用的。但是布局一般只发生在添加新child的过程中,而滑动效果则发生在paint过程中。

void _paintWithContext(PaintingContext context, Offset offset) {// 重新布局就不需要调整offset了.if (_needsLayout)return;_needsPaint = false;paint(context, offset);
}

Viewport通过PaintingContext间接持有Canvas进行绘制。Offset指笛卡尔坐标系下的坐标,与Axis方向无关。绘制时只需改变对应RenderObject的Offset即可实现滚动的效果, 这样就不必重新创建RenderObject。所以我们如果想实现性能较高的列表视图,就要尝试去减少重新布局Child。在对Flutter的列表布局有了基本了解后,我们再来看瀑布流的实现过程。

瀑布流的实现逻辑

WatetfallFlow的布局过程中需要指定Child的Offset,然后对其进行布局。所以需要继承SliverMultiBoxAtaptor,依赖于其将SliverConstraints转换为BoxConstraints的能力。我们也可以使用其SliverBoxChildManager, 方便控制Child的懒加载过程。

核心逻辑

在瀑布流中由于同一行(列)的child(大多)具有先后关系,需要按照顺序来进行布局,所以瀑布流相比于GridView更类似于ListView,而瀑布流的布局过程也借鉴了ListView。整个瀑布流的布局逻辑围绕三个核心展开:

  1. 在滑动的过程中找到其边缘最近的child,在其后(前)进行添加child,并对child进行layout.
  2. 在child离开一定距离后进行GC.
  3. 保证layout方法被尽可能少的调用. 上文有提过layout会调用performLayout而不能直接进行paint.

其中核心的数据结构是ParentData.

ParentData位于Child中,Child将其传递给Sliver,Sliver又将其传递至上层,其中储存了全部的布局信息(在笛卡尔坐标系下)。在performLayout中,child在调用layout时所使用的布局信息就来自ParentData。在Child的添加过程中,用一个Manager存储前后边缘所有Child的ParentData,在添加时寻找边缘最靠近可见区域的Child,对其ParentData进行设置并替换当前Child.

布局的核心逻辑是对从最开始的Child(对应firstIndex)到最末的Child(对应targetLastIndex)进行布局。如果_layoutedChilds中已经有记录,则跳过其布局过程。

for (int index = firstIndex; index <= targetLastIndex; ++index) {final SliverGeometry gridGeometry = layout.getGeometryForChildIndex(index);final BoxConstraints childConstraints = gridGeometry.getBoxConstraints(constraints);RenderBox child = childAfter(trailingChildWithLayout);if (child == null || indexOf(child) != index) {// 重新获取Child.child = _createAndLayoutChildIfNeeded(childConstraints, after: trailingChildWithLayout);if (child != null && indexOf(child) == index) {_layoutedChilds.add(index);}else if (child == null) {// Child已经用尽.break;}} else {if (!_layoutedChilds.contains(index)) {_layoutChildIfNeeded(child, parentUsesSize: true);_layoutedChilds.add(index);}}trailingChildWithLayout = child;
}

对离开视图的child进行GC,同时记得将数组中的child清除.

if (firstChild != null) {// 上一次的最先最末Child.final int oldFirstIndex = indexOf(firstChild);final int oldLastIndex = indexOf(lastChild);// 前后需要GC的child数量final int leadingGarbage = (firstIndex - oldFirstIndex).clamp(0, childCount);final int trailingGarbage = targetLastIndex == null ? 0 : (oldLastIndex - targetLastIndex).clamp(0, childCount);// GCcollectGarbage(leadingGarbage, trailingGarbage);_layoutedChilds.sort();_layoutedChilds.removeRange(0, leadingGarbage);_layoutedChilds.removeRange(layoutedChilds.length - 1 - trailingGarbage, layoutedChilds.length - 1);
} else {collectGarbage(0, 0);
}

在开发过程中出现了帧数偏低的问题,发现是Child在performLayout的过程中会出现重复布局。解决方法是我们不仅记录leading, trailing边缘的child。而且用对已经layout过的child进行记录,粗暴直接但是有效,这样做也可以提供单独update单个child的Layout能力。在更新Child的布局时也只需从记录中将对应child移除。

相比于原生视图,我们可以通过获取所有Child的ParentData信息,可以为上层接口提供实时并且有效的回调.。这样就可以根据每个Child的实时位置来提供生命周期,曝光打点的能力。所以可以对每个child的坐标进行监听,从而获得精准的曝光信息。

从瀑布流到容器

在瀑布流的开发过程中也暴露出了一些设计上的问题。

比如瀑布流的具体渲染逻辑都在RenderObject中进行,太过底层显然是不利于业务方根据业务进行定制。

又比如由于没有复用的机制,在视图层级较为复杂时帧数会由于重复渲染而不可避免的降低。

image.png

借鉴native思路重新设计后将整体容器分为3个部分进行设计。

  1. delegate

主要管理child生命周期并响应手势,由于我们可以得到每个可见Child的parentData属性,所以可在滚动时进行实时的通知。从而对每个Child的位置监听,从开始创建进入缓冲区,到从缓冲区进入可见区域。手势则来自于顶层的Scrollable。

  1. layout

主要负责布局所有的Child。将具体的布局逻辑抽离出,类似于iOS中的UICollectionViewLayout。但是在开发过程中也出现了一些问题,原因主要来自于Flutter特殊的信息传递方式,就是我们不能采用native的方式一次性计算出所有child的布局。因为RenderBox需要接收一个BoxConstraints才能返回一个size。

  1. reuser

reuser则在RenderObject层面,对Child进行基于类型的复用并实现局部更新的操作。需要将SliverMultiBoxAdaptor和其Element拷贝一份进行重写,改变其mount的逻辑,方案还在探索和调研之中,希望能在后续的文章中和大家见面!

性能数据

应用于主搜索页进行自动化测试,先前在54.7帧左右,换用瀑布流后为56.2,大概提升了1.5帧。

image-20200715201457291

内存上则有略微的升高情况。

展望

目前Flutter的列表视图中仍然有很多问题需要处理,比如瀑布流中scrollTo(int index)的能力还无法实现,内存的使用情况等和原生相比仍然有不小的差距, 对于Flutter侧的复用的稳定性和兼容性上还存在问题,闲鱼在Flutter化上还有很多路要走。

PS0: 文中代码基于Flutter 1.12.13。

PS1: 文中譬如Viewport,既代指Widget本身, 又代指其对应的RenderObject。

PS2: 文中涉及到的代码经过删改, 仅供参考。

 

原文链接:https://developer.aliyun.com/article/767965?utm_content=g_1000168249
本文为阿里云原创内容,未经允许不得转载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/515334.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

find VS not looking for VS2015

用管理员打开cmd. npm install -g node-gyp npm install --global --production windows-build-tools

uniapp 制作手机app程序, 使用uni.chooseVideo录制视频,视频播放模糊分辨率低的原因

原因:compressed参数是true&#xff0c;这个参数的意思是‘是否压缩所选的视频源文件&#xff0c;默认值为 true&#xff0c;需要压缩’。如果不加这个参数默认就是true&#xff0c;就会默认压缩视频播放出来就是模糊的&#xff0c;改为false就可以了 可以参考uniapp API开发文…

闲鱼的商品结构化是如何演进的(超详细)

作者&#xff1a;闲鱼技术-吴白 引言 商品的信息结构化程度在某种意义上来说决定导购效率的天花板。闲鱼商品结构化和淘宝/天猫最大的区别在于闲鱼卖家都是个人用户&#xff0c;无论是专业程度还是行动力远不及淘宝卖家。为了不阻碍商品发布&#xff0c;闲鱼一直倡导轻发布&a…

一文看懂5G射频的“黑科技”

作者 | 小枣君来源 | 鲜枣课堂&#xff08;ID&#xff1a;xzclasscom&#xff09;手机&#xff0c;作为移动互联网时代的标配&#xff0c;已经走进了我们每个人的生活。有了它&#xff0c;我们可以随心所欲地聊天、购物、追剧&#xff0c;享受美好的人生。正因为手机如此重要&a…

抖音实战~首页视频~下拉刷新

文章目录1. 配置下拉刷新2. 下拉刷新监听3. tab监听索引4. 控制台监控5. 父子组件调用6. 效果图1. 配置下拉刷新 在pages.json配置文件中添加如下配置&#xff1a; "enablePullDownRefresh":true //开启下拉刷新2. 下拉刷新监听 // 当前页下拉刷新onPullDownRefre…

支付宝研究员王益的建议:“学好语文,才能写好代码”

王益&#xff0c;蚂蚁集团研究员&#xff0c;开源项目SQLFlow 和 ElasticDL 的负责人。他从10岁开始写代码。曾经用自己焊接的电路板扩展“中华学习机”来把自家的老式“威力牌”双筒洗衣机改造成了自动洗衣机&#xff1b;用Apple BASIC语言和6502汇编混合编程写了人生中第一个…

抖音实战~搜索页面~视频详情

文章目录一、前端部分1. 检索关键词短视频列表2. 选中某一个短视频3. 短视频详情二、后端部分2.1. 短视频入口2.2. 短视频接口层2.3. 短视频服务层2.4. 持久层-接口2.5. 持久层- xml三、效果图鉴赏3.1. 搜索页面3.2. 短视频列表3.3. 短视频详情一、前端部分 1. 检索关键词短视…

金蝶国际公布2020年全年业绩,云业务收入增长45.6%

金蝶国际软件集团有限公司&#xff08;「金蝶国际」、「金蝶」或「公司」&#xff0c;连同其附属公司统称「集团」&#xff1b;股份编号&#xff1a;0268.HK&#xff09;今日公布其截至2020年12月31日&#xff08;「报告期」&#xff09;之全年业绩。集团持续推进业务发展转型战…

技术人的灵魂 3 问,阿里工程师如何解答?

作者 | 氐宿 阿里云高级前端技术专家 导读&#xff1a;在业务团队做事的工程师摸爬滚打了一段时间后&#xff0c;一定会有所疑问。团队同学在最初的一段时间都提出这样的疑惑&#xff1a;如何在业务中发现有技术价值的问题&#xff1f;发现问题后如何思考和发起再到解决&…

10+知识图谱开放下载,让你的学习效率提升5倍! | “右脑”开发套餐

简介&#xff1a; 为了让广大开发者清晰了解技术体系&#xff0c;打造属于自己的系统学习路径。今天&#xff0c;开发者社区整理了10知识图谱&#xff0c;供大家交流学习&#xff0c;持续更新中~ 知识的学习从来就不是孤立的&#xff0c;学习任何知识&#xff08;概念、定义、…

抖音实战~分享模块~短视频下载(保存到相册)

文章目录一、可见范围1. 自己发布短视频2. 其他人发布短视频二、源码分析2.1. 底部窗口popup2.2. 实现组件uni-popup 弹出层2.3. 插件涉及组件2.4. 组件改造2.5. 关键的api三、作品鉴赏3.1. 自己发布视频3.2. 其他人发布视频3.3. 保存短视频一、可见范围 保存到相册、复制链接、…

HDC.Cloud 2021剧透:六大创新产品、开发者年度盛宴即将开启

编辑 | 宋慧 出品 | CSDN云计算 头图来源 | HDC.Cloud官网 今日&#xff0c;华为开发者大会2021&#xff08;Cloud&#xff09;&#xff08;简称HDC.Cloud 2021&#xff09;媒体预沟通会在京召开&#xff0c;华为技术有限公司高级副总裁、云与计算BG副总裁张顺茂在会上表示&am…

抖音实战~分享模块~复制短视频链接

文章目录一、可见范围1. 自己发布短视频2. 其他人发布短视频二、源码分析2.1. 底部窗口popup2.2. 实现组件uni-popup 弹出层2.3. 插件涉及组件2.4. 组件改造2.5. 关键的api三、作品鉴赏3.1. 自己发布视频3.2. 复制链接一、可见范围 保存到相册、复制链接、二维码 都是前端完成的…

莉莉丝《剑与远征》:基于阿里云全站加速提升用户体验

公司介绍 莉莉丝游戏致力为全球玩家创造好玩的游戏&#xff0c;坚持“全球化”与“品类进化”核心战略&#xff0c;通过对全球市场与品类发展阶段的深度理解&#xff0c;将国产手游带向了世界&#xff0c;代表作&#xff1a;《小冰冰传奇》&#xff0c;《万国觉醒》&#xff0c…

亚马逊云科技首次发布“三驾马车”中国业务战略

2021年3月25日&#xff0c;亚马逊云科技今天在北京举行媒体沟通会&#xff0c;首次正式发布“三驾马车&#xff0c;齐头并进”的中国业务战略。通过亚马逊云科技独一无二的全球优势&#xff0c;发挥亚马逊全球业务体系的强大支撑&#xff0c;并利用亚马逊的创新文化和方略&…

KubeCon 2020 阿里云推出四大企业级容器新品 ,详解云原生操作系统进化

导读&#xff1a;云原生操作系统进化&#xff0c;详解阿里云 ACK Pro、ASM、ACR EE、ACKEdge 等四款企业级容器新品。 KubeCon 2020 中国站&#xff0c;阿里云容器服务负责人易立会在《云原生&#xff0c;数字经济技术创新基石》的演讲中&#xff0c;分享阿里云原生如何助力数字…

抖音实战~分享模块~生成短视频二维码

文章目录一、可见范围1. 自己发布短视频2. 其他人发布短视频二、源码分析2.1. 底部窗口popup2.2. 实现组件uQRCode2.3. 插件涉及组件2.4. 组件改造2.5. 实现原理三、作品鉴赏3.1. 生成短视频二维码3.2. 微信扫描3.3. 抖音扫描一、可见范围 保存到相册、复制链接、二维码 都是前…

轻松处理高于平常10倍的视频需求,还能节省60%的IT成本,蓝墨做对了什么?

近年来&#xff0c;Serverless 一直在高速发展&#xff0c;并呈现出越来越大的影响力。主流的云服务商也在不断地丰富云产品体系&#xff0c;提供更好的开发工具&#xff0c;更高效的应用交付流水线&#xff0c;更好的可观测性&#xff0c;更细腻的产品间集成&#xff0c;但一切…

PassMark 更新排行,苹果 M1 杀疯了

整理 | 寇雪芹出品 | CSDN云计算&#xff08;ID&#xff1a;CSDNcloud&#xff09;3月24 日&#xff0c;PassMark 网站更新了单核性能排行榜&#xff0c;苹果 M1 3.2GHz 首次上榜就直逼榜首。苹果 M1 杀疯了以数千个 PerformanceTest 基准测试结果为数据基础&#xff0c;PassMa…

uni-app使用canvas绘制二维码

文章目录1. 插件选型2. 页面部分3.事件部分1. 插件选型 uqrCode 点击安装即可 https://ext.dcloud.net.cn/search?quqrCode 2. 页面部分 template&#xff1a; <view class"canvas-qrcode-box" style"align-self: center;"><canvas id"q…