1. 程式人生 > >背水一戰 Windows 10 (57) - 控件(集合類): ListViewBase - 增量加載, 分步繪制

背水一戰 Windows 10 (57) - 控件(集合類): ListViewBase - 增量加載, 分步繪制

fun 設置 enter priority protected 最大 卡頓 tinc abc

原文:背水一戰 Windows 10 (57) - 控件(集合類): ListViewBase - 增量加載, 分步繪制

[源碼下載]


背水一戰 Windows 10 (57) - 控件(集合類): ListViewBase - 增量加載, 分步繪制



作者:webabcd


介紹
背水一戰 Windows 10 之 控件(集合類 - ListViewBase)

  • 增量加載
  • 分步繪制(大數據量流暢滾動)



示例
1、ListViewBase 的增量加載
Controls/CollectionControl/ListViewBaseDemo/MyIncrementalLoading.cs

/*
 * 演示如何實現 ISupportIncrementalLoading 接口,以便為 ListViewBase 的增量加載提供數據
 * 
 * 
 * ISupportIncrementalLoading - 用於支持增量加載
 *     HasMoreItems - 是否還有更多的數據
 *     IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count) - 異步加載指定數量的數據(增量加載)
 *    
 * LoadMoreItemsResult - 增量加載的結果
 *     Count - 實際已加載的數據量
 
*/ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Runtime.InteropServices.WindowsRuntime; using System.Threading.Tasks; using Windows.Foundation; using Windows.UI.Core; using Windows.UI.Xaml; using Windows.UI.Xaml.Data; namespace Windows10.Controls.CollectionControl.ListViewBaseDemo {
public class MyIncrementalLoading<T> : ObservableCollection<T>, ISupportIncrementalLoading { // 是否正在異步加載中 private bool _isBusy = false; // 提供數據的 Func // 第一個參數:增量加載的起始索引;第二個參數:需要獲取的數據量;第三個參數:獲取到的數據集合 private Func<int, int, List<T>> _funcGetData; // 最大可顯示的數據量 private uint _totalCount = 0; /// <summary> /// 構造函數 /// </summary> /// <param name="totalCount">最大可顯示的數據量</param> /// <param name="getDataFunc">提供數據的 Func</param> public MyIncrementalLoading(uint totalCount, Func<int, int, List<T>> getDataFunc) { _funcGetData = getDataFunc; _totalCount = totalCount; } /// <summary> /// 是否還有更多的數據 /// </summary> public bool HasMoreItems { get { return this.Count < _totalCount; } } /// <summary> /// 異步加載數據(增量加載) /// </summary> /// <param name="count">需要加載的數據量</param> /// <returns></returns> public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count) { if (_isBusy) { throw new InvalidOperationException("忙著呢,先不搭理你"); } _isBusy = true; var dispatcher = Window.Current.Dispatcher; return AsyncInfo.Run ( (token) => Task.Run<LoadMoreItemsResult> ( async () => { try { // 模擬長時任務 await Task.Delay(1000); // 增量加載的起始索引 var startIndex = this.Count; await dispatcher.RunAsync ( CoreDispatcherPriority.Normal, () => { // 通過 Func 獲取增量數據 var items = _funcGetData(startIndex, (int)count); foreach (var item in items) { this.Add(item); } } ); // Count - 實際已加載的數據量 return new LoadMoreItemsResult { Count = (uint)this.Count }; } finally { _isBusy = false; } }, token ) ); } } }

Controls/CollectionControl/ListViewBaseDemo/ListViewBaseDemo3.xaml

<Page
    x:Class="Windows10.Controls.CollectionControl.ListViewBaseDemo.ListViewBaseDemo3"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Windows10.Controls.CollectionControl.ListViewBaseDemo"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="Transparent" Margin="10 0 10 10">

        <TextBlock Name="lblMsg" />

        <ListView x:Name="listView" Width="300" Height="300" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0 30 0 0">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <Border Background="Blue" Width="200" CornerRadius="3" HorizontalAlignment="Left">
                        <TextBlock Text="{Binding Name}" />
                    </Border>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>

        <TextBlock Name="lblLog" Margin="0 350 0 0" />

    </Grid>
</Page>

Controls/CollectionControl/ListViewBaseDemo/ListViewBaseDemo3.xaml.cs

/*
 * ListViewBase(基類) - 列表控件基類(繼承自 Selector, 請參見 /Controls/SelectionControl/SelectorDemo.xaml)
 *     IncrementalLoadingTrigger - 增量加載的觸發器
 *         Edge - 允許觸發增量加載,默認值
 *         None - 禁止觸發增量加載
 *     DataFetchSize - 預提數據的大小,默認值 3.0
 *         本例將此值設置為 4.0 ,其效果為(註:本例中的 ListView 每頁可顯示的數據量為 6 條或 7 條,以下計算需基於此)
 *         1、先獲取 1 條數據,為的是盡量快地顯示數據
 *         2、再獲取 4.0 * 1 條數據
 *         3、再獲取 4.0 * (6 或 7,如果 ListView 當前顯示了 6 條數據則為 6,如果 ListView 當前顯示了 7 條數據則為 7) 條數據
 *         4、以後每次到達閾值後,均增量加載 4.0 * (6 或 7,如果 ListView 當前顯示了 6 條數據則為 6,如果 ListView 當前顯示了 7 條數據則為 7) 條數據
 *     IncrementalLoadingThreshold - 增量加載的閾值,默認值 0.0
 *         本例將此值設置為 2.0 ,其效果為(註:本例中的 ListView 每頁可顯示的數據量為 6 條或 7 條)
 *         1、滾動中,如果已準備好的數據少於 2.0 * (6 或 7,如果 ListView 當前顯示了 6 條數據則為 6,如果 ListView 當前顯示了 7 條數據則為 7) 條數據,則開始增量加載  
 *         
 *         
 * 本例用於演示如何實現 ListViewBase 的增量加載(數據源需要實現 ISupportIncrementalLoading 接口,詳見:MyIncrementalLoading.cs)
 */

using Windows.UI.Xaml.Controls;
using System.Linq;
using System.Collections.Specialized;
using System;
using Windows10.Common;
using Windows.UI.Xaml;

namespace Windows10.Controls.CollectionControl.ListViewBaseDemo
{
    public sealed partial class ListViewBaseDemo3 : Page
    {
        // 實現了增量加載的數據源
        private MyIncrementalLoading<Employee> _employees;

        public ListViewBaseDemo3()
        {
            this.InitializeComponent();

            this.Loaded += ListViewBaseDemo3_Loaded;
        }

        private void ListViewBaseDemo3_Loaded(object sender, RoutedEventArgs e)
        {
            listView.IncrementalLoadingTrigger = IncrementalLoadingTrigger.Edge;
            listView.DataFetchSize = 4.0;
            listView.IncrementalLoadingThreshold = 2.0;

            _employees = new MyIncrementalLoading<Employee>(1000, (startIndex, count) =>
            {
                lblLog.Text += string.Format("從索引 {0} 處開始獲取 {1} 條數據", startIndex, count);
                lblLog.Text += Environment.NewLine;

                return TestData.GetEmployees().Skip(startIndex).Take(count).ToList();
            });

            _employees.CollectionChanged += _employees_CollectionChanged;

            listView.ItemsSource = _employees;
        }

        void _employees_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            lblMsg.Text = "已獲取的數據量:" + _employees.Count.ToString();
        }
    }
}


2、ListViewBase 的分步繪制
Controls/CollectionControl/ListViewBaseDemo/ListViewBaseDemo4.xaml

<Page
    x:Class="Windows10.Controls.CollectionControl.ListViewBaseDemo.ListViewBaseDemo4"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Windows10.Controls.CollectionControl.ListViewBaseDemo"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="Transparent">

        <!--
            ListViewBase(基類) - 列表控件基類
                ContainerContentChanging - 數據虛擬化時,項容器的內容發生變化時觸發的事件(僅 ItemsStackPanel 和 ItemsWrapGrid 有效)
        -->
        
        <GridView x:Name="gridView" Margin="10 0 10 10" ContainerContentChanging="gridView_ContainerContentChanging">
            <GridView.ItemTemplate>
                <DataTemplate>
                    <StackPanel Width="80" Height="80" Background="Blue">
                        <Rectangle x:Name="placeholderRectangle" Fill="Red" Height="10" Opacity="0" />
                        <TextBlock x:Name="lblName" Text="{Binding Name}" Foreground="Yellow" />
                        <TextBlock x:Name="lblAge" Text="{Binding Age}" Foreground="Aqua" />
                        <TextBlock x:Name="lblIsMale" Text="{Binding IsMale}" Foreground="Gray" />
                    </StackPanel>
                </DataTemplate>
            </GridView.ItemTemplate>
        </GridView>
        
    </Grid>
</Page>

Controls/CollectionControl/ListViewBaseDemo/ListViewBaseDemo4.xaml.cs

/*
 * ListViewBase(基類) - 列表控件基類(繼承自 Selector, 請參見 /Controls/SelectionControl/SelectorDemo.xaml)
 *     ContainerContentChanging - 數據虛擬化時,項容器的內容發生變化時觸發的事件(僅 ItemsStackPanel 和 ItemsWrapGrid 有效)
 * 
 * 
 * 當 ListViewBase 的一屏需要顯示的數據量極大時(一屏的 item 多,且每個 item 中的 element 也多),由於每次滾動時需要繪制當前屏的每個 element,這需要占用大量的 ui 資源,所以就會有一些卡頓
 * 為了解決這個問題 uwp 給出了兩種解決方案
 * 1、設置 ListViewBase 的 ShowsScrollingPlaceholders 屬性為 true(默認值),每次顯示 item 時先顯示占位符(尚不清楚怎麽修改這個占位符的背景色),然後再繪制內容
 *    相關演示請參見:/Controls/CollectionControl/ListViewBaseDemo/ListViewBaseDemo1.xaml
 * 2、通過 ListViewBase 的 ContainerContentChanging 事件,分步繪制 item 中的 element
 *    本例即介紹這種方法。註意在 uwp 中已經不用這麽麻煩了,可以通過 x:Bind 和 x:Phase 來實現,請參見:/Bind/PhaseDemo.xaml
 * 
 * 
 * 本例用於演示如何實現 ListViewBase 的分步繪制(大數據量流暢滾動)
 * 
 * 
 * 註:
 * 虛擬化布局控件用於減少創建的 item 數量
 * 分步繪制用於在繪制 item 時,分階段繪制 item 上的元素
 */

using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Navigation;
using Windows.UI.Xaml.Shapes;
using Windows10.Common;

namespace Windows10.Controls.CollectionControl.ListViewBaseDemo
{
    public sealed partial class ListViewBaseDemo4 : Page
    {
        public ListViewBaseDemo4()
        {
            this.InitializeComponent();
        }

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            gridView.ItemsSource = TestData.GetEmployees(1000);

            // 默認值是 true,即為了保證流暢,每次顯示 item 時先會顯示占位符(application 級的背景色塊),然後再繪制內容
            // 本例演示 ContainerContentChanging 事件的使用,所以不會用到這個
            gridView.ShowsScrollingPlaceholders = false;
        }

        private void gridView_ContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
        {
            // 交由我處理吧(不用系統再處理了)
            args.Handled = true;

            // 第 1 階段繪制
            // args.Phase.ToString(); // 0

            StackPanel templateRoot = (StackPanel)args.ItemContainer.ContentTemplateRoot;
            Rectangle placeholderRectangle = (Rectangle)templateRoot.FindName("placeholderRectangle");
            TextBlock lblName = (TextBlock)templateRoot.FindName("lblName");
            TextBlock lblAge = (TextBlock)templateRoot.FindName("lblAge");
            TextBlock lblIsMale = (TextBlock)templateRoot.FindName("lblIsMale");

            // 顯示自定義占位符(也可以不用這個,而是直接顯示 item 的背景)
            placeholderRectangle.Opacity = 1;

            // 除了占位符外,所有 item 全部暫時不繪制
            lblName.Opacity = 0;
            lblAge.Opacity = 0;
            lblIsMale.Opacity = 0;

            // 開始下一階段的繪制
            args.RegisterUpdateCallback(ShowName);
        }

        private void ShowName(ListViewBase sender, ContainerContentChangingEventArgs args)
        {
            // 第 2 階段繪制
            // args.Phase.ToString(); // 1

            Employee employee = (Employee)args.Item;
            SelectorItem itemContainer = (SelectorItem)args.ItemContainer;
            StackPanel templateRoot = (StackPanel)itemContainer.ContentTemplateRoot;
            TextBlock lblName = (TextBlock)templateRoot.FindName("lblName");

            // 繪制第 2 階段的內容
            lblName.Text = employee.Name;
            lblName.Opacity = 1;

            // 開始下一階段的繪制
            args.RegisterUpdateCallback(ShowAge);
        }

        private void ShowAge(ListViewBase sender, ContainerContentChangingEventArgs args)
        {
            // 第 3 階段繪制
            // args.Phase.ToString(); // 2

            Employee employee = (Employee)args.Item;
            SelectorItem itemContainer = (SelectorItem)args.ItemContainer;
            StackPanel templateRoot = (StackPanel)itemContainer.ContentTemplateRoot;
            TextBlock lblAge = (TextBlock)templateRoot.FindName("lblAge");

            // 繪制第 3 階段的內容
            lblAge.Text = employee.Age.ToString();
            lblAge.Opacity = 1;

            // 開始下一階段的繪制
            args.RegisterUpdateCallback(ShowIsMale);
        }

        private void ShowIsMale(ListViewBase sender, ContainerContentChangingEventArgs args)
        {
            // 第 4 階段繪制
            // args.Phase.ToString(); // 3

            Employee employee = (Employee)args.Item;
            SelectorItem itemContainer = (SelectorItem)args.ItemContainer;
            StackPanel templateRoot = (StackPanel)itemContainer.ContentTemplateRoot;
            Rectangle placeholderRectangle = (Rectangle)templateRoot.FindName("placeholderRectangle");
            TextBlock lblIsMale = (TextBlock)templateRoot.FindName("lblIsMale");

            // 繪制第 4 階段的內容
            lblIsMale.Text = employee.IsMale.ToString();
            lblIsMale.Opacity = 1;

            // 隱藏自定義占位符
            placeholderRectangle.Opacity = 0;
        }
    }
}



OK
[源碼下載]

背水一戰 Windows 10 (57) - 控件(集合類): ListViewBase - 增量加載, 分步繪制