windows phone Virtualized Canvas提供更高效灵活的列表框,它是否是正确的方法?

时间:2013-02-06 21:54:56

标签: windows-phone-7 windows-phone-8 windows-phone

背景: 我正在开发应用程序。其中一个页面是新闻源。新闻中的数据是动态的:它可能包含多个文本块,根据内容为每个新闻项目计算许多图像和布局。

由于不可能(我不知道如何)使它与ListBox / DataTemplate / Binding一起工作,我采用了更低级别的方法并试图实现Virtualized Canvas: https://dl.dropbox.com/u/16063542/MyVirtualizingPanel.cs

想法很简单:

每个包含的项目都实现:

public interface IVirtualizable     {         void ChangeState(VirtualizableState newState);

    VirtualizableState CurrentState { get; }

    double FixedHeight { get; }

    FrameworkElement View { get; }

    Thickness Margin { get; }
}

-panel应放入ScrollViewer并初始化:

public void InitializeWithScrollViewer(ScrollViewer _scrollViewer)
    {
        _listScrollViewer = _scrollViewer;
        EnsureBoundToScrollViewer();
    }

    protected void EnsureBoundToScrollViewer()
    {
        Binding binding = new Binding();
        binding.Source = _listScrollViewer;
        binding.Path = new PropertyPath("VerticalOffset");
        binding.Mode = BindingMode.OneWay;
        this.SetBinding(ListVerticalOffsetProperty, binding);


    }

Panel实现AddItems方法:

public void AddItems(IEnumerable _itemsToBeAdded)         {

        double topMargin = 0;


        foreach (var itemToBeAdded in _itemsToBeAdded)
        {

            itemToBeAdded.View.Margin = new Thickness(0, itemToBeAdded.Margin.Top + topMargin, 0, 0);

            _virtualizableItems.Add(itemToBeAdded);


            topMargin += itemHeightIncludingMargin;
        }

        Height = topMargin;

    }

然后根据滚动位置的变化加载和卸载项目:

private void LoadItemsInSegment(Segment segment,VirtualizableState desiredState)         {             for(int i = segment.LowerBound; i< = segment.UpperBound; i ++)             {                 var item = _virtualizableItems [i];

            item.ChangeState(desiredState);

            if (!Children.Contains(item.View))
            {
                Children.Add(item.View);
            }
        }
    }

private void LoadItemsInSegment(Segment segment,VirtualizableState desiredState)         {             for(int i = segment.LowerBound; i< = segment.UpperBound; i ++)             {                 var item = _virtualizableItems [i];

            item.ChangeState(desiredState);

            if (!Children.Contains(item.View))
            {
                Children.Add(item.View);
            }
        }
    }

 private void UnloadItemsInSegment(Segment segment)
    {
        for (int i = segment.LowerBound; i <= segment.UpperBound; i++)
        {
            var item = _virtualizableItems[i];


            Children.Remove(item.View);

            item.ChangeState(VirtualizableState.Unloaded);

        }
    }

或多或少,它有效。然而,随着布局变得更加复杂并且获得多个图像,滚动性能变得有些差。除此之外,在不时渲染时会出现明显的问题:突然(对于一帧)元素出现错位,看起来像是“眨眼”,不知道如何解释它。无论如何,我的问题是:这种方法最初是否存在缺陷? 或者我应该继续尝试使它工作?

1 个答案:

答案 0 :(得分:0)

我认为您的虚拟化面板不会虚拟化任何内容。

在上下文中,虚拟化以下列方式工作。您需要显示10000个数据项。该屏幕仅适合15个UI元素。系统提供的VirtualizingStackPanel创建~17个UI元素,当用户滚动列表时重用这些元素。当UI元素离开屏幕时,面板会“释放”UI元素(如果需要,可以进行干涉,请参阅ClearContainerForItemOverride方法)。并为以后的重用做好准备(同样,如果需要,可以进行干预,请参阅PrepareContainerForItemOverride)。如果需要更多元素(例如,因为数据项变得更小),则创建新的UI元素。

重点是你有10000个虚拟物品,而实际物品只有~17个,因此虚拟化。

但是在我的代码中,我看到了

public interface IVirtualizable
{
    FrameworkElement View { get; }
}

这意味着您的数据项和UI元素之间存在一对一的关系。 UI元素很昂贵,它们拥有GPU纹理和其他系统资源。这就是为什么当你添加更多元素时,你会遇到性能问题。

重新实现VirtualizingStackPanel很难。它的代码比MyVirtualizingPanel类多3-4倍。幸运的是你不需要它,它已经在框架中,并且它非常通用。如果由于选择/未选择的内容而不喜欢列表框,请使用普通的ItemsControl,并将ItemContainer设置为VirtualizingStackPanel。