zoukankan      html  css  js  c++  java
  • 博客园客户端(Universal App)开发随笔 -- 增量加载 (incremental loading)

    在我们的应用(博客园UAP)中,加入了ListView上拉获取更多内容的功能(GridView也可以),这个功能是通过ISupportIncrementalLoading接口实现的,这是个从Windows 8就开始提供的接口(当然你可以通过ScrollViewer来实现这个功能,只不过稍微麻烦点,还要自己再封装。。)。

    这个接口的定义十分简单:

    public interface ISupportIncrementalLoading
        {
    
            bool HasMoreItems { get; }
    
    
            IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count);
        }

    LoadMoreItemsAsync是由ListView自动调用(当IncrementalLoadingTrigger = IncrementalLoadingTrigger.Edge时), count参数表示每次取多少个项目,这个参数由ListView.DataFetchSizeListView.IncrementalLoadingThreshold决定的,HasMoreItems用来告诉ListView是否还有更多的项目可以加载,False的话即使ListView已经滚到底,也不会再触发LoadMoreIemsAsync。

    PS:使用ISupportIncrementalLoading的过程中,你会发现LoadMoreItemsAsync被连续调用了2次,第一次调用的时候总是count=1,这是因为ListView需要用第一个项目来确定虚拟化项目的数量(关于虚拟化的介绍有点多,请参考MSDN,简单点说效果就是你的ListView/GridView即使要显示几万个项目,内存也不会爆掉)。这对于MSDN上的sample来说是没什么问题的,因为数据出来的很快,但是在需要联网的应用中你会发现初始只有一个项目,过一会儿其他的几十个又出来了,虽然这么做是有理由的,但看上去有点诡异,所以我们在实现中忽略掉了count参数,每次都加载固定数量项目。

    实现

    MSDN上提供了一个简单通用的实现方式,这个sample通过实现IList来存放数据,然后再实现INotifyCollectionChanged来通知项目的变化。

    public abstract class IncrementalLoadingBase: IList, ISupportIncrementalLoading, INotifyCollectionChanged 
        { 
            // 省略IList的实现
            
            // 省略INotifyCollectionChanged的实现
    
            public bool HasMoreItems 
            { 
                get { return HasMoreItemsOverride(); } 
            } 
     
            public Windows.Foundation.IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count) 
            { 
                if (_busy) 
                { 
                    throw new InvalidOperationException("Only one operation in flight at a time"); 
                } 
     
                _busy = true; 
     
                return AsyncInfo.Run((c) => LoadMoreItemsAsync(c, count)); 
            } 
    
            async Task<LoadMoreItemsResult> LoadMoreItemsAsync(CancellationToken c, uint count) 
            { 
                try 
                { 
                    var items = await LoadMoreItemsOverrideAsync(c, count); 
                    var baseIndex = _storage.Count; 
     
                    _storage.AddRange(items); 
     
                    NotifyOfInsertedItems(baseIndex, items.Count); 
     
                    return new LoadMoreItemsResult { Count = (uint)items.Count }; 
                } 
                finally 
                { 
                    _busy = false; 
                } 
            } 
    }

    为了简化实现,我们通过继承 ObservableCollection<T>来达到存放项目、通知变化的目的,并且添加了OnLoadMoreStarted和OnLoadMoreCompleted事件,方便对UI进行更新操作(比如进度条)。

    public abstract class IncrementalLoadingBase<T> : ObservableCollection<T>, ISupportIncrementalLoading
        {
            public bool HasMoreItems
            {
                get { return HasMoreItemsOverride(); }
            }
    
            public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
            {
                if (_busy)
                {
                    throw new InvalidOperationException("Only one operation in flight at a time"); 
                }
    
                _busy = true;
    
                return AsyncInfo.Run((c) => LoadMoreItemsAsync(c, count));
            }
    
            protected async Task<LoadMoreItemsResult> LoadMoreItemsAsync(CancellationToken c, uint count)
            {
                try
                {
                    // 加载开始事件
                    if (this.OnLoadMoreStarted != null)
                    {
                        this.OnLoadMoreStarted(count);
                    }
    
                    var items = await LoadMoreItemsOverrideAsync(c, count);
    
                    AddItems(items);
    
                    // 加载完成事件
                    if (this.OnLoadMoreCompleted != null)
                    {
                        this.OnLoadMoreCompleted(items == null ? 0 : items.Count);
                    }
    
                    return new LoadMoreItemsResult { Count = items == null ? 0 : (uint)items.Count };
                }
                finally
                {
                    _busy = false;
                }
            }
    
    
            public delegate void LoadMoreStarted(uint count);
            public delegate void LoadMoreCompleted(int count);
    
            public event LoadMoreStarted OnLoadMoreStarted;
            public event LoadMoreCompleted OnLoadMoreCompleted;
    
            /// <summary>
            /// 将新项目添加进来,之所以是virtual的是为了方便特殊要求,比如不重复之类的
            /// </summary>
            protected virtual void AddItems(IList<T> items)
            {
                if (items != null)
                {
                    foreach (var item in items)
                    {
                        this.Add(item);
                    }
                }
            }
    
            protected abstract Task<IList<T>> LoadMoreItemsOverrideAsync(CancellationToken c, uint count);
    
            protected abstract bool HasMoreItemsOverride();
    
    
            protected bool _busy = false;
        }

    这样一个简单的增量加载的基类就有了,只需要在子类中实现LoadMoreItemsOverrideAsync方法,并绑定到ListView.ItemsSource即可。

    使用效果如下两张图(请注意首页右上角的数字变化和右下的滚动条):

       

    你喜欢MVVM?那你就需要在这个类的基础上进行下修改了,把取数据的逻辑从中分离,并当作一个collection使用,下面是一个简单的实现。

    public class IncrementalLoadingCollection<T> : ObservableCollection<T>, ISupportIncrementalLoading
    {
        // 这里为了简单使用了Tuple<IList<T>, bool>作为返回值,第一项是新项目集合,第二项是否还有更多,也可以自定义实体类
        Func<uint, Task<Tuple<List<T>, bool>>> _dataFetchDelegate = null;
    
        public IncrementalLoadingCollection(Func<uint, Task<Tuple<List<T>, bool>>> dataFetchDelegate)
        {
            if (dataFetchDelegate == null) throw new ArgumentNullException("dataFetchDelegate");
    
            this._dataFetchDelegate = dataFetchDelegate;
        }
    
        public bool HasMoreItems
        {
            get;
            private set;
        }
    
        public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
        {
            if (_busy)
            {
                throw new InvalidOperationException("Only one operation in flight at a time");
            }
    
            _busy = true;
    
            return AsyncInfo.Run((c) => LoadMoreItemsAsync(c, count));
        }
    
        protected async Task<LoadMoreItemsResult> LoadMoreItemsAsync(CancellationToken c, uint count)
        {
            try
            {
                if (this.OnLoadMoreStarted != null)
                {
                    this.OnLoadMoreStarted(count);
                }
    
                // 我们忽略了CancellationToken,因为我们暂时不需要取消,需要的可以加上
                var result = await this._dataFetchDelegate(count);
    
                var items = result.Item1;
    
                if (items != null)
                {
                    foreach (var item in items)
                    {
                        this.Add(item);
                    }
                }
    
                // 是否还有更多
                this.HasMoreItems = result.Item2;
    
                // 加载完成事件
                if (this.OnLoadMoreCompleted != null)
                {
                    this.OnLoadMoreCompleted(items == null ? 0 : items.Count);
                }
    
                return new LoadMoreItemsResult { Count = items == null ? 0 : (uint)items.Count };
            }
            finally
            {
                _busy = false;
            }
        }
    
    
        public delegate void LoadMoreStarted(uint count);
        public delegate void LoadMoreCompleted(int count);
    
        public event LoadMoreStarted OnLoadMoreStarted;
        public event LoadMoreCompleted OnLoadMoreCompleted;
    
        protected bool _busy = false;
    }

    现在这个家伙变成了一个collection,创建实例的时候传入对应的代理,然后在你的View里面把他绑定到ItemsSource上吧。

    var collection = new IncrementalLoadingCollection<string>(count =>
                {
                    // 对应的get more逻辑
    
                    return Task.Run(() => Tuple.Create(new List<string> { "我是假数据" }, true));
                });

    小结

    增量加载可以在需要显示大量数据的时候,给用户提供一个更平滑的体验,而通过使用ISupportIncrementalLoading,我们只需要实现取数据的逻辑,什么时候调用就不需要我们关心了。欢迎大家继续关注。

    Windows Phone Store App link:

    http://www.windowsphone.com/zh-cn/store/app/博客园-uap/500f08f0-5be8-4723-aff9-a397beee52fc

     

    Windows Store App link:

    http://apps.microsoft.com/windows/zh-cn/app/c76b99a0-9abd-4a4e-86f0-b29bfcc51059

     

    GitHub open source link:

    https://github.com/MS-UAP/cnblogs-UAP

     

    MSDN Sample Code:

    https://code.msdn.microsoft.com/CNBlogs-Client-Universal-477943ab

  • 相关阅读:
    DFS总结
    cmake-make-gcc(g++)
    std::function
    basic_string定义的相关string函数
    欧拉路径和欧拉回路
    正则表达式
    C++ Data Types
    关于uniapp的插槽
    关于微信H5 分享配置
    h5请求的时候总是会跨域
  • 原文地址:https://www.cnblogs.com/ms-uap/p/4155601.html
Copyright © 2011-2022 走看看