在滚动结束时创建带有LoadMoreItemsSync的ListView
本文关键字:LoadMoreItemsSync ListView 创建 滚动 结束 | 更新日期: 2023-09-27 18:28:52
我的Windows Phone 8.1应用程序中有一个ListView
,我可以得到大约1000个或更多的结果,所以每次滚动到底部时,我需要实现"加载更多"功能,或者其他一些逻辑和自然的方式来触发向列表中添加更多项目
我发现ListView
支持ISupportIncrementalLoading
,并发现了以下实现:https://marcominerva.wordpress.com/2013/05/22/implementing-the-isupportincrementalloading-interface-in-a-window-store-app/这是我发现的更好的解决方案,因为它没有指定类型,也就是说,它是泛型的
我对这个解决方案的问题是,当ListView被加载时,LoadMoreItemsAsync
会运行所需的所有时间,直到它得到所有结果,这意味着LoadMore不是由用户触发的。我不确定是什么触发了LoadMoreItemsAsync
,但有些地方是不对的,因为它假设当我打开页面并当场加载所有项目时会发生这种情况,而我不需要做任何事情或滚动。以下是实现:IncrementalLoadingCollection.cs
public interface IIncrementalSource<T> {
Task<IEnumerable<T>> GetPagedItems(int pageIndex, int pageSize);
void SetType(int type);
}
public class IncrementalLoadingCollection<T, I> : ObservableCollection<I>, ISupportIncrementalLoading where T : IIncrementalSource<I>, new() {
private T source;
private int itemsPerPage;
private bool hasMoreItems;
private int currentPage;
public IncrementalLoadingCollection(int type, int itemsPerPage = 10) {
this.source = new T();
this.source.SetType(type);
this.itemsPerPage = itemsPerPage;
this.hasMoreItems = true;
}
public bool HasMoreItems {
get { return hasMoreItems; }
}
public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count) {
var dispatcher = Window.Current.Dispatcher;
return Task.Run<LoadMoreItemsResult>(
async () => {
uint resultCount = 0;
var result = await source.GetPagedItems(currentPage++, itemsPerPage);
if(result == null || result.Count() == 0) {
hasMoreItems = false;
}
else {
resultCount = (uint)result.Count();
await dispatcher.RunAsync(
CoreDispatcherPriority.Normal,
() => {
foreach(I item in result)
this.Add(item);
});
}
return new LoadMoreItemsResult() { Count = resultCount };
}).AsAsyncOperation<LoadMoreItemsResult>();
}
}
这是PersonModelSource.cs
public class DatabaseNotificationModelSource : IIncrementalSource<DatabaseNotificationModel> {
private ObservableCollection<DatabaseNotificationModel> notifications;
private int _type = "";
public DatabaseNotificationModelSource() {
//
}
public void SetType(int type) {
_type = type;
}
public async Task<IEnumerable<DatabaseNotificationModel>> GetPagedItems(int pageIndex, int pageSize) {
if(notifications == null) {
notifications = new ObservableCollection<DatabaseNotificationModel>();
notifications = await DatabaseService.GetNotifications(_type);
}
return await Task.Run<IEnumerable<DatabaseNotificationModel>>(() => {
var result = (from p in notifications select p).Skip(pageIndex * pageSize).Take(pageSize);
return result;
});
}
}
我对它做了一些更改,因为对数据库的调用是异步的,这是我找到的确保在填充集合之前可以等待查询的唯一方法。
在我的DatabaseNotificationViewModel.cs
中
IncrementalNotificationsList = new IncrementalLoadingCollection<DatabaseNotificationModelSource, DatabaseNotificationModel>(type);
一切都很好,除了不太正常的"加载更多"。我的代码出了什么问题?
我在这里创建了一个关于这个问题的非常简化的示例,并在MSDN论坛上提出了这个问题。老实说,我不知道为什么会发生这种奇怪的行为。
我观察到的
- ListView将首先调用计数为1的
LoadMoreItemsAsync
。我认为这是为了确定单个项目的大小,这样它就可以计算出下一次调用所需的项目数 - 如果ListView运行良好,则对
LoadMoreItemsAsync
的第二次调用应该在第一次调用之后立即发生,但项目数正确(计数>1),然后除非向下滚动,否则不会再调用LoadMoreItemsAsync
。然而,在您的示例中,它可能会错误地再次调用计数为1的LoadMoreItemsAsync
- 在最坏的情况下,ListView将继续一次又一次地调用计数为1的
LoadMoreItemsAsync
,直到HasMoreItems
变为false,在这种情况下,它一次加载一个所有项。当这种情况发生时,ListView加载项目时会出现明显的UI延迟不过UI线程没有被阻止。ListView只是通过顺序调用LoadMoreItemsAsync
来占用UI线程 - 不过,ListView不会总是耗尽所有项目。有时它会加载100、200或500个项目。在每种情况下,模式都是:多次调用
LoadMoreItemsAsync(1)
,然后再调用LoadMoreItemsAsync(> 1)
(如果之前的调用没有加载所有项) - 它似乎只发生在页面加载时
- 此问题在Windows Phone 8.1和Windows 8.1上持续存在
问题的原因
- 问题似乎是在将项目添加到列表之前,
LoadMoreItemsAsync
方法中的等待时间非常短的任务(将项目添加至列表后等待任务是可以的) - 如果删除
LoadMoreItemsAsync
中的所有await
,从而强制其同步执行,则不会出现此问题。具体来说,如果移除dispatcher.RunAsync
包装器和await source.GetPagedItems
(只是模拟这些项),那么ListView将表现良好 - 删除所有
await
后,即使您添加的只是一个看似无害的await Task.Run(() => {})
,问题也会再次出现。真奇怪
如何解决问题
如果LoadMoreItemsAsync
调用的大部分时间都在等待对下一页项目的HTTP请求,就像我预计的大多数应用程序一样,那么问题就不会发生。因此,我们可以通过等待Task.Delay(10)
来延长在该方法中花费的时间,比如:
await Task.WhenAll(Task.Delay(10), dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
foreach (I item in result)
this.Add(item);
}).AsTask());
我只是为您的示例提供了一个(巧妙的)变通方法,但没有解释原因如果有人知道为什么会发生这种情况,请告诉我
这并不是导致此问题的唯一原因。如果您的ListView位于ScrollViewer中,它将继续加载所有项目,并且也不会正确虚拟化,从而对性能产生负面影响。解决方案是给ListView一个特定的高度。