原理:
实现了ISupportIncrementalLoading 接口
完成了增量加载,
针对于本地对象无法释放的情况 增加了 相关的Func
同时 通过VisualTree 拿到了GridView中 HorizontalBar 来对滚动条的位置进行捕捉 与计算
通过计算后 来执行 虚化操作
如需 转载请声明博客作者
以下是源码:
IncrementalLoadingCollection 对象:
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:
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;
}
}
}
使用方式:
<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 释放本地流
_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;
});
当增量滚动时 我会针对 触发增量加载事件同时 记录 需要虚化的offset 在回滚的同时 进行释放操作,以保证内存在较低的负载。
在超宽屏幕上的使用效果, 载入了大量本地图片