在滚动结束时创建带有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);


一切都很好,除了不太正常的"加载更多"。我的代码出了什么问题?

在滚动结束时创建带有LoadMoreItemsSync的ListView

我在这里创建了一个关于这个问题的非常简化的示例,并在MSDN论坛上提出了这个问题。老实说,我不知道为什么会发生这种奇怪的行为。

我观察到的

  • ListView将首先调用计数为1的LoadMoreItemsAsync。我认为这是为了确定单个项目的大小,这样它就可以计算出下一次调用所需的项目数
  • 如果ListView运行良好,则对LoadMoreItemsAsync的第二次调用应该在第一次调用之后立即发生,但项目数正确(计数>1),然后除非向下滚动,否则不会再调用LoadMoreItemsAsync。然而,在您的示例中,它可能会错误地再次调用计数为1LoadMoreItemsAsync
  • 在最坏的情况下,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一个特定的高度。

相关文章:
  • 没有找到相关文章