主题
大列表渲染 (Large Lists)
在大多数情况下,你可以使用 Column 和 Row 控件来展示列表。但如果列表中包含成百上千甚至上万个子项,使用 Column 和 Row 将变得非常低效,并会导致界面卡顿。因为它们会一次性渲染所有的子项,即使这些子项在当前的滚动位置上并不可见。
在下面的示例中,我们向页面添加了 5,000 个文本控件。页面使用 Column 作为默认的布局容器:
python
import flet as ft
def main(page: ft.Page):
for i in range(5000):
page.controls.append(ft.Text(f"Line {i}"))
page.scroll = "always"
page.update()
ft.run(main, view=ft.AppView.WEB_BROWSER)运行该程序,你会注意到,它不仅在初始加载并渲染页面上的所有文本行时需要花费几秒钟的时间,而且滚动的体验也非常缓慢且卡顿:

为了展示包含大量子项的列表,我们应该使用 ListView 和 GridView 控件,它们会根据需要进行“按需渲染” —— 仅渲染在当前滚动位置可见的子项。
ListView
ListView 可以是垂直的(默认)或水平的。ListView 中的子项会沿着滚动方向一个接一个地显示。
ListView 已经在底层实现了高效的子项按需渲染,但如果你能为所有子项设置相同的固定高度(或水平 ListView 的宽度)(称为 "extent"),滚动性能还可以得到进一步的提升。你可以通过设置 item_extent 属性来指定绝对大小,或者将 first_item_prototype 设置为 True 来使所有子项的大小与第一个子项的大小一致。
下面让我们使用 ListView 控件输出包含 5,000 个子项的列表:
python
import flet as ft
def main(page: ft.Page):
lv = ft.ListView(expand=True, spacing=10)
for i in range(5000):
lv.controls.append(ft.Text(f"Line {i}"))
page.add(lv)
ft.run(main, view=ft.AppView.WEB_BROWSER)现在,滚动变得非常流畅且迅速,完全能够跟上鼠标的移动速度:

注意
我们在 ListView 的构造函数中使用了 expand=True。为了使其正常工作,ListView 必须指定高度(如果是水平滚动则为宽度)。你可以设置一个绝对大小,例如 ListView(height=300, spacing=10),但在上面的示例中,我们让 ListView 占用页面上的所有可用空间,即 expand。阅读更多关于 Control.expand 属性的信息。
GridView
GridView 允许将控件排列成一个可滚动的网格。
你可以通过 ft.Column(wrap=True) 或 ft.Row(wrap=True) 来制作一个“网格”,例如:
python
import os
import flet as ft
os.environ["FLET_WS_MAX_MESSAGE_SIZE"] = "8000000"
def main(page: ft.Page):
r = ft.Row(wrap=True, scroll="always", expand=True)
page.add(r)
for i in range(5000):
r.controls.append(
ft.Container(
ft.Text(f"Item {i}"),
width=100,
height=100,
alignment=ft.Alignment.CENTER,
bgcolor=ft.Colors.AMBER_100,
border=ft.Border.all(1, ft.Colors.AMBER_400),
border_radius=ft.BorderRadius.all(5),
)
)
page.update()
ft.run(main, view=ft.AppView.WEB_BROWSER)
尝试滚动并调整浏览器窗口的大小 —— 页面是可以正常工作的,但会非常卡顿。
注意
在程序开始时,我们将 FLET_WS_MAX_MESSAGE_SIZE 环境变量的值设置为了 8000000 —— 这是渲染页面的 Flet 服务器可以接收的 WebSocket 消息的最大字节数(默认大小为 1 MB)。由于描述 5,000 个容器控件的 JSON 消息大小会超过 1 MB,因此我们将允许的上限提高到了 8 MB。
然而,通过 WebSocket 通道传输过大的消息通常不是一个好主意,因此建议使用**分批更新(Batched updates)**的方法来控制通道负载。
与 ListView 类似,GridView 能够非常高效地渲染大量的子项。让我们使用 GridView 重新实现上面的示例:
python
import os
import flet as ft
os.environ["FLET_WS_MAX_MESSAGE_SIZE"] = "8000000"
def main(page: ft.Page):
gv = ft.GridView(expand=True, max_extent=150, child_aspect_ratio=1)
page.add(gv)
for i in range(5000):
gv.controls.append(
ft.Container(
ft.Text(f"Item {i}"),
alignment=ft.Alignment.CENTER,
bgcolor=ft.Colors.AMBER_100,
border=ft.Border.all(1, ft.Colors.AMBER_400),
border_radius=ft.BorderRadius.all(5),
)
)
page.update()
ft.run(main, view=ft.AppView.WEB_BROWSER)
使用 GridView 后,滚动和窗口大小调整都变得非常流畅和灵敏!
你可以通过 runs_count 属性指定固定的行数或列数,或者通过 max_extent 属性指定“网格块”的最大尺寸,从而让列数根据屏幕宽度自动调整。在我们的示例中,我们将网格块的最大宽度设置为 150 像素,并通过 child_aspect_ratio=1 将其形状设置为“正方形”。child_aspect_ratio 是每个子项的横轴(cross-axis)尺寸与主轴(main-axis)尺寸的比例。尝试将其更改为 0.5 或 2 查看效果。
分批更新 (Batch updates)
当调用 page.update() 时,一条包含自上次 page.update() 以来页面更新内容的消息会被发送到 Flet 服务器。发送包含数千个新增控件的巨大消息可能会导致用户在消息被完整接收并渲染前等待数秒钟。
为了提高程序的可用性,并尽快向用户展示结果,你可以将页面更新分批发送。例如,以下程序以 500 个子项为一批,将 5,100 个文本控件分批添加至 ListView 中:
python
import flet as ft
def main(page: ft.Page):
# 首先将 ListView 添加到页面中
lv = ft.ListView(expand=1, spacing=10, item_extent=50)
page.add(lv)
for i in range(5100):
lv.controls.append(ft.Text(f"Line {i}"))
# 分批更新页面
if i % 500 == 0:
page.update()
# 将剩余部分发送到页面
page.update()
ft.run(main, view=ft.AppView.WEB_BROWSER)