在滚动结束时使用LoadMoreItemsAsync创建ListView
我在Windows Phone 8.1应用程序中有一个ListView
,我可以有1000个或更多的结果,所以每次滚动到底时我需要实现一个Load Morefunction,或者一些其他逻辑和自然方式触发添加更多列表中的项目。
我发现ListView
支持ISupportIncrementalLoading
,并发现了这个实现: https : //marcominerva.wordpress.com/2013/05/22/implementing-the-isupportincrementalloading-interface-in-a-window-store-app/这是我找到的更好的解决方案,因为它没有指定类型,即它是通用的。
我对此解决方案的问题是,当ListView被加载时, LoadMoreItemsAsync
运行所需的所有时间,直到获得所有结果,这意味着用户不会触发加载更多。 我不确定是什么使得LoadMoreItemsAsync
触发器,但是有些东西是不对的,因为它假设当我打开页面并在现场加载所有项目时,没有我做任何事情或任何滚动。 这是实施:
IncrementalLoadingCollection.cs
public interface IIncrementalSource { Task<IEnumerable> GetPagedItems(int pageIndex, int pageSize); void SetType(int type); } public class IncrementalLoadingCollection : ObservableCollection, ISupportIncrementalLoading where T : IIncrementalSource, 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 LoadMoreItemsAsync(uint count) { var dispatcher = Window.Current.Dispatcher; return Task.Run( 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(); } }
这是PersonModelSource.cs
public class DatabaseNotificationModelSource : IIncrementalSource { private ObservableCollection notifications; private int _type = ""; public DatabaseNotificationModelSource() { // } public void SetType(int type) { _type = type; } public async Task<IEnumerable> GetPagedItems(int pageIndex, int pageSize) { if(notifications == null) { notifications = new ObservableCollection(); notifications = await DatabaseService.GetNotifications(_type); } return await Task.Run<IEnumerable>(() => { var result = (from p in notifications select p).Skip(pageIndex * pageSize).Take(pageSize); return result; }); } }
我改变了一下,因为对我的数据库的调用是异步的,这是我发现确保在填充集合之前可以等待查询的唯一方法。
在我的DatabaseNotificationViewModel.cs
IncrementalNotificationsList = new IncrementalLoadingCollection(type);
一切都很好,除了不那么正常的“加载更多”。 我的代码有什么问题?
我在这里创建了一个非常简单的问题示例,并在MSDN论坛上提出了这个问题。 老实说,我不知道为什么会发生这种奇怪的行为。
我观察到了什么
- ListView将首先调用
LoadMoreItemsAsync
,计数为1.我假设这是为了确定单个项目的大小,以便它可以计算出请求下次调用的项目数。 - 如果ListView表现良好,第二次调用
LoadMoreItemsAsync
应该在第一次调用后立即发生,但具有正确的项目数(count> 1),然后除非向下滚动,否则不会再调用LoadMoreItemsAsync
。 但是,在您的示例中,它可能会再次错误地调用LoadMoreItemsAsync
并将计数值设置为1。 - 在最坏的情况下,实际上在您的示例中经常发生的是,ListView将继续按
LoadMoreItemsAsync
调用LoadMoreItemsAsync
,计数为1,直到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
s后,即使您添加的所有内容都看似无害,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());
我只为你的例子提供了(hacky)解决方法,但没有解释原因。 如果有人知道为什么会这样,请告诉我。
这不是导致此问题的唯一因素。 如果您的ListView位于ScrollViewer中,它将继续加载所有项目,并且不会正确虚拟化,从而对性能产生负面影响。 解决方案是为ListView提供特定的高度。