背水一戰 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
[源碼下載]


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM