【问题标题】:ListView recycling virtualization performance with variable item's size可变项目大小的 ListView 回收虚拟化性能
【发布时间】:2016-09-04 07:48:01
【问题描述】:

将 ListView 与数千个这样的项目一起使用:

<ListView ItemsSource="{Binding Items}"
                 VirtualizingPanel.ScrollUnit="Pixel"
                 VirtualizingPanel.VirtualizationMode="Recycling"
                 VirtualizingPanel.CacheLength="1,1">
    <ListView.ItemTemplate>
        <DataTemplate>
            <local:ItemUserControl />
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

如果我让ItemUserControl 中的每个项目具有不同的高度,我发现滚动非常慢。通过拖动拇指快速滚动时,每一帧大约需要 200-250 毫秒 来进行布局。

如果我将 VirtualizationMode 更改为 Standard,它会下降到 70-110 毫秒。
如果我另外将 ScrollUnit 更改为 Item 它会下降到 30-70 毫秒。
但是,如果我保留所有内容并简单地强制每个项目都相同 Height,它会下降到 5-7 毫秒

此外,我发现在列表顶部滚动时,不同高度的滚动速度更快,而在底部滚动时慢得多。

我的猜测是,当重用 ItemUserControl 时,它的 Height 会发生变化,它会导致整个 ListView 的布局更新。尽管这并不能真正解释为什么它在开始时比在结束时表现更好。我也觉得这样的速度差异有点难以置信。

有人对 WPF 的布局系统有更深入的了解,并能解释为什么会这样吗?也许是实现

【问题讨论】:

  • 刚刚检查 - 您是否将 ListView 的 设置为 VirtualizingStackPanel ?
  • @DeanChalk 感谢您的健全性检查,根据msdn.microsoft.com/en-us/library/…,默认值已经是 VirtualizingStackPanel。虚拟化工作正常,滚动时太慢了。

标签: wpf performance listview layout


【解决方案1】:

感谢 DeanChalk 质疑我的理智,这让我质疑 WPF 的理智,这导致了这个答案。

事实证明,虽然ListView 有不同大小的项目的问题,但ItemsControl 没有。 ListView 继承自 ItemsControl,但一定是有什么东西导致了问题。

所以使用ItemsControl 就是答案。

此代码将为ItemsControl 启用虚拟化,并使用与以前相同的指标,在​​快速滚动时以大约 20 毫秒的时间执行布局。

<ItemsControl ItemsSource="{Binding Items}"
              VirtualizingPanel.ScrollUnit="Pixel"
              VirtualizingPanel.VirtualizationMode="Recycling"
              VirtualizingPanel.CacheLength="1,1"
              VirtualizingPanel.IsVirtualizing="True"
              ScrollViewer.CanContentScroll="True">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <local:ItemUserControl />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
    <ItemsControl.Template>
        <ControlTemplate>
            <ScrollViewer>
                <ItemsPresenter />
            </ScrollViewer>
        </ControlTemplate>
    </ItemsControl.Template>
</ItemsControl>

它似乎仍然比它可能的速度慢,但它是可用的。
滚动顶部和底部时的速度差异仍然存在,我无法真正解释。如果有人有一些见解,请发表评论。

【讨论】:

  • 我遇到了同样的问题,我的物品的高度不一样。我试过 VirtualizationMode="Recycling" 但它不起作用。当我上下滚动时,我看到我的项目的高度不符合预期。应该短的会变高,反之亦然
  • @DhinneshJeevan 我不记得我还做了什么,但这里的代码应该可以工作。我在代码中看到的唯一额外内容是使用自定义 ItemsControl 类来禁用大小缓存,而是在请求时直接提供它(因为我可以更快地计算它)。
【解决方案2】:

这一切都归结为调整/定位滚动位置所需的数学运算。

A) 如果项目都是固定大小的,并且滚动是像素:

  • 滚动高度 = Items.Count * Item.Height
  • 第一个可见项目 = 滚动偏移量(像素)/Item.Height

B) 如果项目是可变高度的,但滚动是按项目:

  • 滚动高度 = Items.Count
  • 第一个可见项目 = 滚动偏移(项目)

C) 如果项目是可变高度的,并且滚动是像素:

  • 滚动高度 = 所有项目的 Item.Height 之和
  • 第一个可见项目 = 滚动偏移量(像素)- 前 N 个项目的高度;必须搜索 N。

对于选项 C 可以进行一些优化,并且可能会或可能不会内置到 WPF 中:

  • 滚动高度可能是近似的,而不是准确的。也许对迄今为止测量的项目使用平均项目高度。
  • 第一个可见项目的计算可以通过二分搜索完成,但仍可能需要实际测量第一个可见项目之上的所有项目。

基本上,行为 C 将始终是虚拟化项目列表的最坏情况,因为无论如何它都可能需要加载所有项目,只是为了进行布局测量。

【讨论】:

    猜你喜欢
    • 2016-07-05
    • 1970-01-01
    • 2020-07-07
    • 2012-03-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多