当我们使用GridView和ListView加载大量数据的时候,为了提高列表的效率,就会用到虚拟化技术。虚拟化技术会计算可见项的数量,然后只为可见项创建UI。GridView和ListView是已经实现虚拟化的。但是为什么有时候没效果呢?看看下文也许对你有所帮助。

一、数据虚拟化结构

ListView和GridView是支持虚拟化的。其他控件只要实现了虚拟化面板(ItemsWarpGrid或ItemsStackPanel)就可以支持虚拟化。但是要注意以下情况,否则虚拟化会失效。

ListView外面嵌套ScrollViewer

1
2
3
<ScrollViewer>
<ListView/>
</ScrollViewer>

像上面这样数据的虚拟化就会失效(其实很多人外面套ScrollViewer是为了实现增量加载)。为什么呢,因为ListView虚拟化结构其实是这样的。

1
2
3
4
5
6
7
8
9
<ListView >
<ListView.Template>
<ControlTemplate>
<ScrollViewer>
<ItemsPresenter />
</ScrollViewer>
</ControlTemplate>
</ListView.Template>
</ListView>

那如果我非要在外面套一个ScrollViewer怎么实现虚拟化呢?可以像下面这样做。去掉ItemsPresenter外面的ScrollViewer.

1
2
3
4
5
6
7
8
9
<ScrollViewer>
<ListView >
<ListView.Template>
<ControlTemplate>
<ItemsPresenter />
</ControlTemplate>
</ListView.Template>
</ListView>
</ScrollViewer>

二、一些说明

例子1:做简单的数据加载的时候,比如下面这个例子(滚动到底部自动加载数据,后记:其实到底部加载数据其实有更优雅的方式,外面套ScrollViewer太土狗了)。这个其实破坏了ListView原有的虚拟化结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<ScrollViewer x:Name="ScrollRoot"
ViewChanged="ScrollRoot_ViewChanged"
ScrollViewer.VerticalScrollMode="Auto">
<ListView SelectionMode="None"
IsItemClickEnabled="True"
ItemsSource="{Binding Allperson}"
ItemTemplate="{StaticResource NumberTemplate}">
<ListView.Template>
<ControlTemplate>
<ItemsPresenter />
</ControlTemplate>
</ListView.Template>
</ListView>
</ScrollViewer>

开启数据虚拟化,首次加载10000个数据,到底部后每次加载1000个,实时可视化树和内存占用如下图所示:

上面的例子很明显虚拟化是生效的。但是我们实际做项目的时候,数据模型可不会这么简单。这时候我们就会发现实时可视化树的数量一直在增加,内存也一直在上涨。似乎虚拟化是失效的。那到底怎样呢?看看下个例子。

例子2:**开眼UWP** 的首页推荐页是一个ListView,ListView里存在多个不同的模板。有的模板里还嵌套着GridView,GridView里还有两个不同的模板。

这个问题就比较棘手,这个页面虽然虚拟化是正常的。但是内存一直很高。达到了将近500MB(一直上下滚动)。直到我看到了官方文档的下面文字:

项目模板选择器 (DataTemplateSelector) 允许应用根据要显示的数据项目类型在运行时返回不同的项目模板。 这使开发更高效,但也使 UI 虚拟化更困难,因为不是每一个项目模板都可重复用于每个数据项目。
在循环使用项目 (ListViewItem/GridViewItem) 时,框架必须确定可用于循环队列(循环队列是当前未用于显示数据的项目的缓存)的项目是否具有可与当前数据项目所需的项目模板匹配的项目模板。 如果循环队列中没有带有相应项目模板的项目,则将创建一个新项目,并为其实例化相应的项目模板。 另一方面,如果循环队列包含带有相应项目模板的项目,则该项目将从循环队列中删除并用于当前数据项目。 在仅使用少量项目模板以及在使用不同项目模板的项目集锦分布均匀的情况下,项目模板选择器有效。
当使用不同项目模板的项目分布不均匀时,可能需要在平移期间创建新项目模板,并且这将取消虚拟化所提供的许多好处。 此外,在评估是否可为当前数据项目重复使用特定的容器时,项目模板选择器仅考虑五个可能的候选项。 因此,在应用中使用项目模板选择器前,你应谨慎考虑你的数据是否适用于项目模板选择器。 如果你的集锦大部分是同类,则选择器将在大多数时间(可能是所有时间)返回相同的类型。 请注意你要为上述罕见的同质例外所付出的代价,并考虑是否首选使用 ChoosingItemContainer(或两个项目控件)。

ChoosingItemContainer目前我还不会使用。等学会了看看是否可以解决这个问题。

例子3:在爱美图中,是一个GridView增量加载数据,在虚拟化被破坏时,内存一直上涨,甚至超过1GB。但是修复虚拟化以后。情况如下:

这个里面也用了模板选择器,不过就一个广告和图片的选择,比较简单。所以虚拟化的性能没怎么影响。