原理: 實現了ISupportIncrementalLoading 接口 完成了增量加載, 針對於本地對象無法釋放的情況 增加了 相關的Func 同時 通過VisualTree 拿到了GridView中 HorizontalBar 來對滾動條的位置進行捕捉 與計算 通過計算後 來執行 虛化操作 如需 轉載請聲明博客作者 以下是源碼: IncrementalLoadingCollection 對象: [csharp] public class IncrementalLoadingCollection<T> : ObservableCollection<T>, ISupportIncrementalLoading { // 是否正在異步加載中 private bool _isBusy = false; // 提供數據的 Func // 第一個參數:增量加載的起始索引;第二個參數:需要獲取的數據量;第三個參數:獲取到的數據集合 private Func<int, int, List<T>> _funcGetData; // 最大可顯示的數據量 private uint _totalCount = 0; private Func<T, T> _actDisposeData; public int PageIndex = 0; public uint perCount = 0; /// <summary> /// 構造函數 /// </summary> /// <param name="totalCount">最大可顯示的數據量</param> /// <param name="getDataFunc">提供數據的 Func</param> public IncrementalLoadingCollection(uint totalCount, Func<int, int, List<T>> getDataFunc, Func<T, T> actDisposeData) { _funcGetData = getDataFunc; _totalCount = totalCount; _actDisposeData = actDisposeData; } /// <summary> /// 是否還有更多的數據 /// </summary> public bool HasMoreItems { get { return this.Count < _totalCount; } } /// <summary> /// 異步加載數據(增量加載) /// </summary> /// <param name="count">需要加載的數據量</param> /// <returns></returns> public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count) { perCount = count; if (_isBusy) { return AsyncInfo.Run((token) => Task.Run<LoadMoreItemsResult>(() => { return new LoadMoreItemsResult { Count = (uint)this.Count }; }, token)); } _isBusy = true; var dispatcher = Window.Current.Dispatcher; return AsyncInfo.Run( (token) => Task.Run<LoadMoreItemsResult>( async () => { try { //// 模擬長時任務 await Task.Delay(100); // 增量加載的起始索引 var startIndex = this.Count; await dispatcher.RunAsync( CoreDispatcherPriority.Normal, () => { PageIndex++; // 通過 Func 獲取增量數據 var items = _funcGetData(startIndex, (int)count); if (items != null) foreach (var item in items) { this.Add(item); } }); // Count - 實際已加載的數據量 return new LoadMoreItemsResult { Count = (uint)this.Count }; } finally { _isBusy = false; } }, token)); } public void DisposeItemByStartAndEnd(long start, long end) { for (long i = start; i < end; i++) { _actDisposeData(this.Items[(int)i]); } } public void RemoveItemByRange(long start, long end) { for (long i = start; i < end; i++) { if (this.Items.Count > i) { this.RemoveItem((int)i); } } } public void Reset() { this.OnCollectionChanged(new System.Collections.Specialized.NotifyCollectionChangedEventArgs(System.Collections.Specialized.NotifyCollectionChangedAction.Reset)); } } IncrementalLoadingGridView: [csharp] public class IncrementalLoadingGridView : GridView { #region Member Variables private ScrollBar _HorizontalScrollBar; private Dictionary<int, int> _pageOffsetDict = new Dictionary<int, int>(); private Dictionary<int, bool> _pageVirtualizingDict = new Dictionary<int, bool>(); private dynamic _filelist; #endregion #region Constants const string HORIZONTALSCROLLBAR_PARTNAME = "HorizontalScrollBar"; #endregion public IncrementalLoadingGridView() { this.Loaded += ((sender, e) => { this.OnApplyTemplate(); }); this.Unloaded += ((sender, e) => { _HorizontalScrollBar = Pollute.FindVisualChildByName<ScrollBar>(this, HORIZONTALSCROLLBAR_PARTNAME); if (null != _HorizontalScrollBar) _HorizontalScrollBar.ValueChanged -= ValueChanged; }); } protected override async void OnApplyTemplate() { base.OnApplyTemplate(); await this.WaitForLayoutUpdateAsync(); _HorizontalScrollBar = Pollute.FindVisualChildByName<ScrollBar>(this, HORIZONTALSCROLLBAR_PARTNAME); if (null != _HorizontalScrollBar) { _HorizontalScrollBar.ValueChanged += ValueChanged; } } protected override async void OnItemsChanged(object e) { if (null != this.ItemsSource) { InitPositionByValue(); } base.OnItemsChanged(e); if (null == this.ItemsSource) { await this.WaitForLoadedAsync(); InitPositionByValue(); } } private void InitPositionByValue() { _filelist = this.ItemsSource; CompositionTarget.Rendering += ((obj, args) => { var newValue = Convert.ToInt32(_HorizontalScrollBar.Value); int currentPageIndex = _filelist.PageIndex - 2; if (!_pageOffsetDict.ContainsKey(currentPageIndex)) { _pageOffsetDict[currentPageIndex] = newValue; _pageVirtualizingDict[currentPageIndex] = false; } }); } private int preIndex = 0; private int maxPageIndex = 0; private int minOffset; private async void ValueChanged(object sender, RangeBaseValueChangedEventArgs e) { if (null == this.ItemsSource) return; var newValue = Convert.ToInt32(e.NewValue); var oldValue = Convert.ToInt32(e.OldValue); if (newValue == oldValue) return; Debug.WriteLine("坐標:" + newValue); int currentPageIndex; if (null == _filelist) _filelist = this.ItemsSource; if (e.NewValue < 1.0) { await Task.Run(async () => { string text = "滑到頭了 開始釋放 釋放前個數:" + _filelist.Count.ToString(); Debug.WriteLine(text); for (int i = 3; i < maxPageIndex; i++) { _pageVirtualizingDict[i] = true; var start = (i - 1) * _filelist.perCount; var end = i * _filelist.perCount - 1; await this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () => { if (_filelist.Count > end) { _filelist.DisposeItemByStartAndEnd(start, end); _filelist.RemoveItemByRange(start, end); } await Task.Delay(500); _filelist.Reset(); }); } _filelist.PageIndex = 2; text = "滑到頭了 釋放完畢 釋放後個數:" + _filelist.Count; Debug.WriteLine(text); }); } else await Task.Run(() => { if (newValue > oldValue) { //lock (_pageOffsetDict) //{ var horiOffset = newValue - oldValue; if (minOffset > horiOffset) minOffset = horiOffset; currentPageIndex = _filelist.PageIndex - 2; //_pageOffsetDict[currentPageIndex] = newValue; maxPageIndex = Convert.ToInt32(_filelist.Count) / Convert.ToInt32(_filelist.perCount); //} if (preIndex != currentPageIndex && _pageOffsetDict.ContainsValue(newValue)) { Debug.WriteLine("坐標:" + newValue + " 上一頁:" + preIndex + " 當前頁:" + currentPageIndex); if (_pageVirtualizingDict.ContainsKey(preIndex)) { _pageVirtualizingDict[preIndex] = false; Debug.WriteLine("@@@@@@@@@@@@@@@@@@@@@@@@@需要向後虛化:" + preIndex + "@@@@@@@@@@@@@@@@@@@@@@" + _pageVirtualizingDict[preIndex]); if (!_pageVirtualizingDict[preIndex] && preIndex > 3) { int i = preIndex; while (i > 3) { //if (!_pageVirtualizingDict.ContainsKey(i)) //{ // _pageVirtualizingDict[i] = false; // _pageOffsetDict[i] = oldValue -= minOffset; //} if (_pageVirtualizingDict.ContainsKey(i) && !_pageVirtualizingDict[i]) { var start = (i - 3) * _filelist.perCount; var end = (i - 2) * _filelist.perCount - 1; _filelist.DisposeItemByStartAndEnd(start, end); _pageVirtualizingDict[i] = true; Debug.WriteLine("虛化完畢:" + i); } i--; } } } preIndex = currentPageIndex; } _pageVirtualizingDict[currentPageIndex] = false; } else if (newValue < oldValue) { if (_pageOffsetDict.ContainsValue(newValue)) { currentPageIndex = _pageOffsetDict.GetKey(newValue).FirstOrDefault(); Debug.WriteLine("當前頁:" + currentPageIndex + " 坐標:" + newValue); var offset = 3; if (preIndex - offset > currentPageIndex && currentPageIndex > 0) { _pageVirtualizingDict[preIndex] = false; if (!_pageVirtualizingDict[preIndex]) { Debug.WriteLine("@@@@@@@@@@@@@@@@@@@@@@@@@虛化 After頁:" + preIndex + " 是否虛化" + _pageVirtualizingDict[preIndex]); int i = preIndex - offset; while (i <= maxPageIndex) { //if (!_pageVirtualizingDict.ContainsKey(i)) //{ // _pageVirtualizingDict[i] = false; // _pageOffsetDict[i] = oldValue += minOffset; //} if (_pageVirtualizingDict.ContainsKey(i) && !_pageVirtualizingDict[i]) { Debug.WriteLine("開始釋放第:" + i + "頁"); int count = _filelist.Count; Debug.WriteLine("虛化前個數:" + count); var start = (i - 1) * _filelist.perCount; var end = i * _filelist.perCount - 1; if (end < _filelist.Count) { _filelist.DisposeItemByStartAndEnd(start, end); } string writeLine = "虛化after完畢 虛化位Start:" + start + " End:" + end + " Page:" + i + " 虛化後個數:" + count; Debug.WriteLine(writeLine); _pageVirtualizingDict[i] = true; } i++; } } _pageVirtualizingDict[currentPageIndex] = false; //_pageVirtualizingDict[currentPageIndex - 1] = false; //_pageVirtualizingDict[currentPageIndex - 2] = false; //_pageVirtualizingDict[currentPageIndex - 3] = false; preIndex = currentPageIndex; } _pageVirtualizingDict[currentPageIndex] = false; } } }); if (e.NewValue == _HorizontalScrollBar.Maximum) { this.IsHitTestVisible = false; await Task.Delay(500); this.IsHitTestVisible = true; } } } 使用方式: [html <toolkit:IncrementalLoadingGridView x:Name="gvMain" Visibility="{Binding IsLeafLevel, Converter={StaticResource BooleanToVisibilityConverter}}" Padding="140,40,0,0" SelectionMode="None" ItemsSource="{Binding ImageItemsSource, Mode=TwoWay}" IncrementalLoadingThreshold="0.5" DataFetchSize="0.5" IsItemClickEnabled="True" PointerMoved="gvMain_PointerMoved" ItemClick="gvMain_ItemClick" ItemTemplateSelector="{StaticResource imageDataTemplateSelector}"> <GridView.ItemsPanel> <ItemsPanelTemplate> <WrapGrid VirtualizingStackPanel.VirtualizationMode="Recycling"></WrapGrid> </ItemsPanelTemplate> </GridView.ItemsPanel> </toolkit:IncrementalLoadingGridView> IncrementalLoadingThreshold="0.5" DataFetchSize="0.5" 這2個閥值設定的比較低 這樣虛化起來效率會高一些 需要binding 的列表對象 Dispose方法 需要binding對象繼承IDispose 釋放本地流 [csharp _filelist = new IncrementalLoadingCollection<FileItem>((uint)_currentfiles.Count, (startIndex, Count) => { SetLoadingState(true); if (_currentfiles == null || _currentfiles.Count <= startIndex) return null; List<FileItem> list = new List<FileItem>(); foreach (var file in _currentfiles.Skip(startIndex).Take(Count)) { FileItem item = new FileItem(); item.File = file; item.Name = file.DisplayName; if (folder.Name.EndsWith("zhx")) item.Name = "zhx" + item.Name; list.Add(item); } SetLoadingState(false); return list; }, (o) => { o.Dispose(); return o; });