多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Flet 大列表渲染性能优化:ListView、GridView 与批量更新实战

Flet 大列表渲染性能优化:ListView、GridView 与批量更新实战 Flet 大列表渲染性能优化ListView、GridView 与批量更新实战【免费下载链接】fletBuild realtime web, mobile and desktop apps in Python only. No frontend experience required.项目地址: https://gitcode.com/gh_mirrors/fl/flet导读在 Flet 中展示包含数百甚至数千个条目的列表时直接使用Column或Row会导致界面卡顿、滚动迟滞。本指南讲解 Flet 针对此类场景的核心方案使用按需渲染的ListView与GridView控件配合item_extent、runs_count、max_extent等性能参数以及通过分批调用page.update()控制 WebSocket 消息体积的批量更新策略。读完本文你将掌握在大数据量列表场景下写出流畅、响应迅速的 Flet 应用的完整实战方法。问题为什么 5000 条记录会让 Column/Row 卡顿在大多数情况下用Column和Row控件展示列表就足够了。但当列表包含数百甚至数千个条目时Column和Row会变得低效、界面明显迟滞——因为它们会一次性渲染全部子控件即使这些控件并不在当前滚动位置可见。下面这个示例向页面添加了 5,000 个文本控件而页面默认使用Column作为布局容器import flet as ft def main(page: ft.Page): for i in range(5000): page.controls.append(ft.Text(fLine {i})) page.scroll always page.update() ft.run(main, viewft.AppView.WEB_BROWSER)运行这段程序后你会发现不仅首次加载并渲染所有文本行需要花上几秒钟滚动时同样又慢又卡。原因在于5,000 个Text控件全部被实例化并参与布局即使其中绝大多数不可见每次滚动都会触发大量控件的重建与绘制超出 GPU 与 UI 线程的处理能力。解决办法展示大量条目的列表时改用ListView和GridView控件——它们只在当前滚动位置可见的范围内按需渲染条目。ListView按需渲染的线性列表ListView既可以是纵向默认也可以是横向horizontalTrue的条目沿滚动方向一个接一个排列。ListView本身已经实现了高效的按需渲染但滚动性能还可以进一步提升如果能让所有条目拥有相同的高度横向ListView则是相同的宽度滚动引擎就能跳过逐项测量直接定位。Flet 提供两种方式方式属性说明绝对尺寸item_extent为所有条目设定统一的固定高度或宽度首项原型first_item_prototypeTrue让所有条目的尺寸等同于第一个子控件的尺寸实战用 ListView 输出 5,000 个条目import flet as ft def main(page: ft.Page): lv ft.ListView(expandTrue, spacing10) for i in range(5000): lv.controls.append(ft.Text(fLine {i})) page.add(lv) ft.run(main, viewft.AppView.WEB_BROWSER)换成ListView之后滚动平滑且足够跟手能实时响应鼠标移动。关键细节ListView 必须具有确定的高度或宽度:::note 上例在ListView构造函数中使用了expandTrue。ListView要正常工作必须指定高度横向时指定宽度。你可以设置绝对尺寸例如ListView(height300, spacing10)但上面的例子是让ListView占满页面所有可用空间即 expand。更多内容参见Control.expand属性。 :::源码级原理builder 按需构建在 Flet 的 Flutter 客户端实现 list_view.dart 中build_controls_on_demand默认开启True此时控件会选用 Flutter 的ListView.builder或ListView.separated变体仅在条目即将滚动进视口时才通过itemBuilder创建对应控件spacing 0时使用ListView.separated并在separatorBuilder中按divider_thickness决定插入Divider还是纯SizedBox间距见 list_view.dartspacing 0时使用ListView.builder并将itemExtent透传给 Flutter 引擎让其在固定条目尺寸下跳过测量见 list_view.dart。Python 侧对等定义见 list_view.py其中明确了item_extent、prototype_item仅在build_controls_on_demandTrue或spacing0时生效first_item_prototypeTrue时客户端会把第一个子控件包装成prototypeItem参与ListView.builder的原型测量见 list_view.dart横向模式且外层高度无界未设置height、expand且未被父级约束时客户端会直接渲染错误提示height is unbounded要求开发者显式给出高度见 list_view.dart。GridView可滚动网格GridView允许把控件排列进一个可滚动的网格中。反例用 Row(wrapTrue) 模拟网格你可以用ft.Column(wrapTrue)或ft.Row(wrapTrue)拼出网格效果例如import os import flet as ft os.environ[FLET_WS_MAX_MESSAGE_SIZE] 8000000 def main(page: ft.Page): r ft.Row(wrapTrue, scrollalways, expandTrue) page.add(r) for i in range(5000): r.controls.append( ft.Container( ft.Text(fItem {i}), width100, height100, alignmentft.Alignment.CENTER, bgcolorft.Colors.AMBER_100, borderft.Border.all(1, ft.Colors.AMBER_400), border_radiusft.BorderRadius.all(5), ) ) page.update() ft.run(main, viewft.AppView.WEB_BROWSER)尝试滚动和缩放浏览器窗口——一切都能工作但非常卡顿。:::note 程序开头设置了环境变量FLET_WS_MAX_MESSAGE_SIZE8000000——这是 Flet Server 渲染页面时可接收的WebSocket 消息最大字节数。默认值为 1 MB而描述 5,000 个Container控件的 JSON 消息会超过 1 MB因此我们把允许值提升到 8 MB。不过通过 WebSocket 通道挤压大消息通常不是好主意请改用下文批量更新的方式来控制通道负载。 :::实战用 GridView 重写上述示例import os import flet as ft os.environ[FLET_WS_MAX_MESSAGE_SIZE] 8000000 def main(page: ft.Page): gv ft.GridView(expandTrue, max_extent150, child_aspect_ratio1) page.add(gv) for i in range(5000): gv.controls.append( ft.Container( ft.Text(fItem {i}), alignmentft.Alignment.CENTER, bgcolorft.Colors.AMBER_100, borderft.Border.all(1, ft.Colors.AMBER_400), border_radiusft.BorderRadius.all(5), ) ) page.update() ft.run(main, viewft.AppView.WEB_BROWSER)与ListView类似GridView渲染大量子控件时非常高效。改用GridView后滚动和窗口缩放都平滑且响应迅速。网格布局参数runs_count 与 max_extent网格的列数/行数runs有两种指定方式runs_count固定交叉轴方向列或行的子控件数量max_extent设置单个格子的最大尺寸运行时列数/行数自动变化以适应容器宽度。上例中我们把格子最大尺寸设为 150 像素并用child_aspect_ratio1将其形状设为正方形。child_aspect_ratio是每个子控件交叉轴与主轴尺寸的比值可以尝试改成0.5或2观察效果差异。源码级原理两种网格代理在 grid_view.dart 中可以看到GridView客户端根据是否设置max_extent选择两种 Flutter 网格代理未设置max_extent时使用SliverGridDelegateWithFixedCrossAxisCount列数固定为runs_count设置了max_extent时改用SliverGridDelegateWithMaxCrossAxisExtent列数由maxExtent与容器宽度自动推导。默认build_controls_on_demandTrue时走GridView.builder条目按需构建见 grid_view.dartPython 侧默认值可查 grid_view.pyruns_count默认 1、max_extent默认None、spacing与run_spacing默认 10、child_aspect_ratio默认 1.0。其类注释也明确建议处理数千条目的场景应优先选择GridView而非 wrap 的Column/Row以获得平滑滚动。批量更新控制 WebSocket 消息负载调用page.update()时Flet 会通过 WebSocket 向 Flet Server 发送一条消息内容包含自上次page.update()以来发生的页面更新。一次性发送包含数千个新增控件的超大消息用户可能要等待几秒钟才能完整接收并渲染。为了提升程序可用性、尽快把结果呈现给用户可以分批发送页面更新。下面的程序把 5,100 个子控件按每批 500 个添加到ListViewimport flet as ft def main(page: ft.Page): # 先把 ListView 添加到页面 lv ft.ListView(expand1, spacing10, item_extent50) page.add(lv) for i in range(5100): lv.controls.append(ft.Text(fLine {i})) # 每 500 条发送一次更新 if i % 500 0: page.update() # 发送剩余的条目 page.update() ft.run(main, viewft.AppView.WEB_BROWSER)注意这个例子同时设置了item_extent50配合每 500 条一次的page.update()分批推送用户能很快看到列表头几百条其余条目在后台持续到达并即时渲染。批量更新的取舍建议批次大小批次越小首屏响应越快但page.update()调用频率越高WebSocket 往返次数也越多需要根据实际条目复杂度权衡本例 500 是一个常用起点超大消息规避FLET_WS_MAX_MESSAGE_SIZE可以把允许的消息上限从默认 1 MB 调大但这只是扩容并非根治——尽量通过批量更新把单条消息控制在合理体积内而不是依赖调大该上限渲染顺序先把容器控件ListView/GridViewpage.add()到页面并先行更新再循环追加子控件并分批更新保证用户能第一时间看到滚动容器本身。总结与选择建议场景推荐方案关键参数数百至数千条线性列表ListViewitem_extent/first_item_prototype注意设置高度或expand数千条网格布局GridViewruns_count或max_extentchild_aspect_ratio一次性添加大量控件分批调用page.update()每批 500 条左右必要时调大FLET_WS_MAX_MESSAGE_SIZE核心结论用ListView/GridView替代Column/Row承载大数据量列表这是 Flet 官方 Cookbooklarge-lists.md给出的推荐做法其底层通过 Flutter 的ListView.builder/GridView.builder按需构建实现见 list_view.dart 与 grid_view.dart再配合固定条目尺寸参数与批量更新策略即可让包含数千条目的 Flet 应用保持流畅滚动与快速响应。【免费下载链接】fletBuild realtime web, mobile and desktop apps in Python only. No frontend experience required.项目地址: https://gitcode.com/gh_mirrors/fl/flet创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表