前些时候,我着手开发一个基于 UWP 的 Imgur 客户端。Imgur 是一个图片分享平台。使用过 Imgur 的朋友可能知道,Imgur 在各种平台上,图片都是使用瀑布流排列的。

Imgur 上的图片均使用瀑布流排列

Windows Community Toolkit 中有 StaggeredPanel 控件,为我们提供了这种布局。

Windows Community Toolkit 中的 StaggeredPanel 布局

我使用了这一布局。但是测试发现性能很差,卡顿的现象十分明显,内存占用也相当高。分析以后发现有以下原因:

  • Imgur 是图片分享平台,页面中媒体元素很多。而且 Imgur 中的动图多以 MP4 格式存储,需要视频播放器。这些媒体元素内存占用量极大。
  • Imgur 的 API 在请求到第二页及以后时,会每页返回 500 项,信息量较大。
  • StaggeredPanel 不支持 UI 虚拟化,集合中所有的项目都被渲染出来,导致内存占用高。
  • 要实现瀑布流布局,本身的计算量比实现简单的网格与列表布局大。

其中,第一条是平台性质决定,无法避免。第四项也是布局需求决定,我们只能尽量优化算法与避免布局计算。有些朋友可能会说,采用代理模式,可以缓解第二条问题。的确如此,但是这种策略并不能根治问题,当用户真的像刷抖音一样翻到第 500 项时,性能差的问题又会出现。

所以我们必须从第三项入手解决问题。这里需要先解释一下什么是 UI 虚拟化。

UI 虚拟化意味着只渲染屏幕上会出现的内容。这里的 “渲染” 指的是将呈现数据的一系列控件构建加载出来。例如,在一个滚动的列表中,只有屏幕上正在显示的内容,以及前后的一部分缓冲区中的内容被渲染出来。在这个视口之外的内容都会被回收,以释放内存资源。通过 UI 虚拟化,可以极大地降低内存占用。

UWP 中内置的 ItemsStackPanelItemsWrapGrid 都支持 UI 虚拟化。前者被用于 ListView 控件,后者被用于 GridView 控件。如果我们能够在 StaggeredPanel 中实现 UI 虚拟化,问题就能得到解决。

不过这么明显的问题,社区怎么可能这么长时间都没有发现呢…… StaggeredPanel 之所以不支持 UI 虚拟化,是因为因为支持 UI 虚拟化的基类 VirtualizingPanel 的构造函数是不公开的,具体可参见这一 GitHub Issue.

于是这一条路就走到头了。不过,就在我一筹莫展时,新大陆出现了: Windows UI Library 的团队在几天前刚刚在一次预览版更新中引入了 ItemsRepeater 控件。

ItemsRepeater 类似于 ListView 与 GridView 两种集合控件。但与两类集合控件不同的是,ItemsRepeater 只提供显示集合内容的功能,不提供选择与交互等功能,这些功能都需要开发人员自行提供。更加基础的功能也带来了更多的定制选项,例如最重要的,ItemsRepeater 支持自定义的 Layout. Layout 类似于 UWP 中内置的 Panel, 提供集合中项目的排列的逻辑。不过与 Panel 不同的是,支持 UI 虚拟化的 VirtualizingLayout 类是完全可以继承扩展的。

当时 ItemsRepeater 以及 Layout 还没有任何 API 文档,已知的信息只有相关的几个类、类成员的名字、官方示例以及内部的测试用例。我通过这些信息,摸索着进行实现,中间遇到了很多问题。不过最终还是成功了。现在,相关的文档已经相当完善,于是我又参照文档检查了我的实现,并将在接下来几次更新中,介绍一下设计实现的思路以及遇到的问题,以期日后提示自己以及有类似问题的朋友们。