在滚动结束时使用LoadMoreItemsAsync创建ListView

时间:2014-11-03 17:29:56

标签: c# xaml listview windows-store-apps windows-phone-8.1

我的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会一直运行所需的所有时间,直到获得所有结果,这意味着用户不会触发加载更多。我不确定是什么使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);


一切都很好,除了不那么正常的“加载更多”。我的代码有什么问题?

2 个答案:

答案 0 :(得分:9)

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

我观察到的内容

  • ListView将首先调用LoadMoreItemsAsync,计数为1.我假设这是为了确定单个项目的大小,以便它可以计算出请求下次调用的项目数。
  • 如果ListView表现良好,第二次调用LoadMoreItemsAsync应该在第一次调用后立即发生,但使用正确的项目数(count> 1),然后不再调用{{1除非你向下滚动,否则会发生。但是,在您的示例中,它可能会错误地再次调用LoadMoreItemsAsync并重新计算
  • 在最糟糕的情况下,实际上在您的示例中经常出现的情况是,ListView将继续按顺序调用LoadMoreItemsAsync计数为1,直到LoadMoreItemsAsync变为false ,在这种情况下,它一次加载一个项目。发生这种情况时,ListView加载项目时会出现明显的UI延迟。但是, UI线程未被阻止。 ListView只是通过顺序调用HasMoreItems 来占用UI线程。
  • ListView并不总是耗尽所有项目。有时它会加载100,或200或500个项目。在每种情况下,模式为:LoadMoreItemsAsync的多次调用,如果先前的调用未加载所有项目,则单次调用LoadMoreItemsAsync(1)
  • 似乎只在页面加载时发生。
  • 该问题在Windows Phone 8.1和Windows 8.1上都是持续存在的。

导致问题的原因

  • 在您将项目添加到列表中之前,LoadMoreItemsAsync(> 1)方法中的问题似乎是非常短暂等待的任务(等待您将项目添加到列表后的任务清单很好。)
  • 如果删除LoadMoreItemsAsync内的所有await,则不会发生此问题,从而强制它同步执行。具体来说,如果删除LoadMoreItemsAsync包装器和dispatcher.RunAsync(只是模拟项目),那么ListView将表现得很好。
  • 删除了所有await source.GetPagedItems后,即使您添加的所有内容都是看似无害的await,问题也会重新出现。多奇怪啊!

如何解决问题

如果在await Task.Run(() => {})通话中花费的大部分时间都在等待下一页项目的HTTP请求,正如我预期的大多数应用程序都是,那么问题就不会发生。所以,我们可以通过等待LoadMoreItemsAsync来延长方法花费的时间,如下所示:

Task.Delay(10)

我只为你的例子提供了(hacky)解决方法,但没有解释原因。 如果有人知道为什么会这样,请告诉我。

答案 1 :(得分:3)

这不是导致此问题的唯一因素。如果您的ListView在ScrollViewer中,它将继续加载所有项目,并且不会正确虚拟化,从而对性能产生负面影响。解决方案是为ListView提供特定的高度。