基于GridView LoadMoreAsync 设计的横向双向瀑布流 组件 源码分享

17 篇文章 0 订阅
9 篇文章 0 订阅

原理:

实现了ISupportIncrementalLoading 接口

完成了增量加载,

针对于本地对象无法释放的情况 增加了 相关的Func

同时 通过VisualTree 拿到了GridView中 HorizontalBar 来对滚动条的位置进行捕捉 与计算

通过计算后 来执行 虚化操作

 

如需 转载请声明博客作者

 

以下是源码:

 

IncrementalLoadingCollection 对象:

 public class IncrementalLoadingCollection<T> : ObservableCollection<T>, ISupportIncrementalLoading
    {
        // 是否正在异步加载中
        private bool _isBusy = false;

        // 提供数据的 Func
        // 第一个参数:增量加载的起始索引;第二个参数:需要获取的数据量;第三个参数:获取到的数据集合
        private Func<int, int, List<T>> _funcGetData;
        // 最大可显示的数据量
        private uint _totalCount = 0;

        private Func<T, T> _actDisposeData;

        public int PageIndex = 0;

        public uint perCount = 0;

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="totalCount">最大可显示的数据量</param>
        /// <param name="getDataFunc">提供数据的 Func</param>
        public IncrementalLoadingCollection(uint totalCount, Func<int, int, List<T>> getDataFunc, Func<T, T> actDisposeData)
        {
            _funcGetData = getDataFunc;
            _totalCount = totalCount;
            _actDisposeData = actDisposeData;
        }

        /// <summary>
        /// 是否还有更多的数据
        /// </summary>
        public bool HasMoreItems
        {
            get { return this.Count < _totalCount; }
        }

        /// <summary>
        /// 异步加载数据(增量加载)
        /// </summary>
        /// <param name="count">需要加载的数据量</param>
        /// <returns></returns>
        public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
        {
            perCount = count;
            if (_isBusy)
            {
                return AsyncInfo.Run((token) => Task.Run<LoadMoreItemsResult>(() =>
                {
                    return new LoadMoreItemsResult { Count = (uint)this.Count };

                }, token));
            }
            _isBusy = true;

            var dispatcher = Window.Current.Dispatcher;

            return AsyncInfo.Run(
                (token) =>
                    Task.Run<LoadMoreItemsResult>(
                       async () =>
                       {
                           try
                           {
                                模拟长时任务
                               await Task.Delay(100);

                               // 增量加载的起始索引
                               var startIndex = this.Count;

                               await dispatcher.RunAsync(
                                    CoreDispatcherPriority.Normal,
                                    () =>
                                    {
                                        PageIndex++;
                                        // 通过 Func 获取增量数据
                                        var items = _funcGetData(startIndex, (int)count);
                                        if (items != null)
                                            foreach (var item in items)
                                            {
                                                this.Add(item);
                                            }
                                    });

                               // Count - 实际已加载的数据量
                               return new LoadMoreItemsResult { Count = (uint)this.Count };
                           }
                           finally
                           {
                               _isBusy = false;
                           }
                       },
                       token));
        }

        public void DisposeItemByStartAndEnd(long start, long end)
        {
            for (long i = start; i < end; i++)
            {
                _actDisposeData(this.Items[(int)i]);
            }
        }

        public void RemoveItemByRange(long start, long end)
        {
            for (long i = start; i < end; i++)
            {
                if (this.Items.Count > i)
                {
                    this.RemoveItem((int)i);
                }
            }
        }

        public void Reset()
        {
            this.OnCollectionChanged(new System.Collections.Specialized.NotifyCollectionChangedEventArgs(System.Collections.Specialized.NotifyCollectionChangedAction.Reset));
        }
    }


IncrementalLoadingGridView:

 

  public class IncrementalLoadingGridView : GridView
    {
        #region Member Variables
        private ScrollBar _HorizontalScrollBar;
        private Dictionary<int, int> _pageOffsetDict = new Dictionary<int, int>();
        private Dictionary<int, bool> _pageVirtualizingDict = new Dictionary<int, bool>();
        private dynamic _filelist;
        #endregion

        #region Constants
        const string HORIZONTALSCROLLBAR_PARTNAME = "HorizontalScrollBar";
        #endregion

        public IncrementalLoadingGridView()
        {
            this.Loaded += ((sender, e) =>
            {
                this.OnApplyTemplate();
            });
            this.Unloaded += ((sender, e) =>
            {
                _HorizontalScrollBar = Pollute.FindVisualChildByName<ScrollBar>(this, HORIZONTALSCROLLBAR_PARTNAME);
                if (null != _HorizontalScrollBar)
                    _HorizontalScrollBar.ValueChanged -= ValueChanged;
            });

        }

        protected override async void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            await this.WaitForLayoutUpdateAsync();
            _HorizontalScrollBar = Pollute.FindVisualChildByName<ScrollBar>(this, HORIZONTALSCROLLBAR_PARTNAME);
            if (null != _HorizontalScrollBar)
            {
                _HorizontalScrollBar.ValueChanged += ValueChanged;
            }
        }

        protected override async void OnItemsChanged(object e)
        {
            if (null != this.ItemsSource)
            {
                InitPositionByValue();
            }

            base.OnItemsChanged(e);
            if (null == this.ItemsSource)
            {
                await this.WaitForLoadedAsync();
                InitPositionByValue();
            }
        }

        private void InitPositionByValue()
        {
            _filelist = this.ItemsSource;
            CompositionTarget.Rendering += ((obj, args) =>
            {
                var newValue = Convert.ToInt32(_HorizontalScrollBar.Value);
                int currentPageIndex = _filelist.PageIndex - 2;
                if (!_pageOffsetDict.ContainsKey(currentPageIndex))
                {
                    _pageOffsetDict[currentPageIndex] = newValue;
                    _pageVirtualizingDict[currentPageIndex] = false;
                }
            });
        }

        private int preIndex = 0;
        private int maxPageIndex = 0;
        private int minOffset;
        private async void ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
        {
            if (null == this.ItemsSource) return;
            var newValue = Convert.ToInt32(e.NewValue);
            var oldValue = Convert.ToInt32(e.OldValue);
            if (newValue == oldValue) return;
            Debug.WriteLine("坐标:" + newValue);
            int currentPageIndex;
            if (null == _filelist) _filelist = this.ItemsSource;
            if (e.NewValue < 1.0)
            {
                await Task.Run(async () =>
                {
                    string text = "滑到头了 开始释放 释放前个数:" + _filelist.Count.ToString();
                    Debug.WriteLine(text);
                    for (int i = 3; i < maxPageIndex; i++)
                    {
                        _pageVirtualizingDict[i] = true;
                        var start = (i - 1) * _filelist.perCount;
                        var end = i * _filelist.perCount - 1;
                        await this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () =>
                        {
                            if (_filelist.Count > end)
                            {
                                _filelist.DisposeItemByStartAndEnd(start, end);
                                _filelist.RemoveItemByRange(start, end);
                            }
                            await Task.Delay(500);
                            _filelist.Reset();
                        });
                    }
                    _filelist.PageIndex = 2;
                    text = "滑到头了 释放完毕 释放后个数:" + _filelist.Count;

                    Debug.WriteLine(text);
                });
            }
            else
                await Task.Run(() =>
                {
                    if (newValue > oldValue)
                    {
                        //lock (_pageOffsetDict)
                        //{
                        var horiOffset = newValue - oldValue;
                        if (minOffset > horiOffset) minOffset = horiOffset;
                        currentPageIndex = _filelist.PageIndex - 2;
                        //_pageOffsetDict[currentPageIndex] = newValue;
                        maxPageIndex = Convert.ToInt32(_filelist.Count) / Convert.ToInt32(_filelist.perCount);

                        //}
                        if (preIndex != currentPageIndex && _pageOffsetDict.ContainsValue(newValue))
                        {
                            Debug.WriteLine("坐标:" + newValue + " 上一页:" + preIndex + " 当前页:" + currentPageIndex);
                            if (_pageVirtualizingDict.ContainsKey(preIndex))
                            {
                                _pageVirtualizingDict[preIndex] = false;
                                Debug.WriteLine("@@@@@@@@@@@@@@@@@@@@@@@@@需要向后虚化:" + preIndex + "@@@@@@@@@@@@@@@@@@@@@@" + _pageVirtualizingDict[preIndex]);
                                if (!_pageVirtualizingDict[preIndex] && preIndex > 3)
                                {
                                    int i = preIndex;
                                    while (i > 3)
                                    {
                                        //if (!_pageVirtualizingDict.ContainsKey(i))
                                        //{
                                        //    _pageVirtualizingDict[i] = false;
                                        //    _pageOffsetDict[i] = oldValue -= minOffset;
                                        //}
                                        if (_pageVirtualizingDict.ContainsKey(i) && !_pageVirtualizingDict[i])
                                        {
                                            var start = (i - 3) * _filelist.perCount;
                                            var end = (i - 2) * _filelist.perCount - 1;
                                            _filelist.DisposeItemByStartAndEnd(start, end);
                                            _pageVirtualizingDict[i] = true;
                                            Debug.WriteLine("虚化完毕:" + i);
                                        }
                                        i--;
                                    }
                                }
                            }
                            preIndex = currentPageIndex;
                        }
                        _pageVirtualizingDict[currentPageIndex] = false;
                    }
                    else if (newValue < oldValue)
                    {
                        if (_pageOffsetDict.ContainsValue(newValue))
                        {
                            currentPageIndex = _pageOffsetDict.GetKey(newValue).FirstOrDefault();
                            Debug.WriteLine("当前页:" + currentPageIndex + " 坐标:" + newValue);
                            var offset = 3;
                            if (preIndex - offset > currentPageIndex && currentPageIndex > 0)
                            {
                                _pageVirtualizingDict[preIndex] = false;
                                if (!_pageVirtualizingDict[preIndex])
                                {
                                    Debug.WriteLine("@@@@@@@@@@@@@@@@@@@@@@@@@虚化 After页:" + preIndex + " 是否虚化" + _pageVirtualizingDict[preIndex]);
                                    int i = preIndex - offset;
                                    while (i <= maxPageIndex)
                                    {
                                        //if (!_pageVirtualizingDict.ContainsKey(i))
                                        //{
                                        //    _pageVirtualizingDict[i] = false;
                                        //    _pageOffsetDict[i] = oldValue += minOffset;
                                        //}
                                        if (_pageVirtualizingDict.ContainsKey(i) && !_pageVirtualizingDict[i])
                                        {
                                            Debug.WriteLine("开始释放第:" + i + "页");
                                            int count = _filelist.Count;
                                            Debug.WriteLine("虚化前个数:" + count);
                                            var start = (i - 1) * _filelist.perCount;
                                            var end = i * _filelist.perCount - 1;
                                            if (end < _filelist.Count)
                                            {
                                                _filelist.DisposeItemByStartAndEnd(start, end);
                                            }
                                            string writeLine = "虚化after完毕 虚化位Start:" + start + " End:" + end + " Page:" + i + " 虚化后个数:" + count;
                                            Debug.WriteLine(writeLine);
                                            _pageVirtualizingDict[i] = true;
                                        }
                                        i++;
                                    }
                                }
                                _pageVirtualizingDict[currentPageIndex] = false;
                                //_pageVirtualizingDict[currentPageIndex - 1] = false;
                                //_pageVirtualizingDict[currentPageIndex - 2] = false;
                                //_pageVirtualizingDict[currentPageIndex - 3] = false;
                                preIndex = currentPageIndex;
                            }
                            _pageVirtualizingDict[currentPageIndex] = false;
                        }
                    }

                });
            if (e.NewValue == _HorizontalScrollBar.Maximum)
            {
                this.IsHitTestVisible = false;
                await Task.Delay(500);
                this.IsHitTestVisible = true;
            }
        }
    }


使用方式:

    <toolkit:IncrementalLoadingGridView x:Name="gvMain"
                      Visibility="{Binding IsLeafLevel, Converter={StaticResource BooleanToVisibilityConverter}}"
                      Padding="140,40,0,0"
                      SelectionMode="None"
                      ItemsSource="{Binding ImageItemsSource, Mode=TwoWay}"
                      IncrementalLoadingThreshold="0.5" DataFetchSize="0.5"
                      IsItemClickEnabled="True"
                      PointerMoved="gvMain_PointerMoved"
                      ItemClick="gvMain_ItemClick"
                     ItemTemplateSelector="{StaticResource imageDataTemplateSelector}">
                <GridView.ItemsPanel>
                    <ItemsPanelTemplate>
                        <WrapGrid VirtualizingStackPanel.VirtualizationMode="Recycling"></WrapGrid>
                    </ItemsPanelTemplate>
                </GridView.ItemsPanel>
            </toolkit:IncrementalLoadingGridView>


IncrementalLoadingThreshold="0.5" DataFetchSize="0.5" 这2个阀值设定的比较低 这样虚化起来效率会高一些

 

需要binding 的列表对象

Dispose方法 需要binding对象继承IDispose 释放本地流

_filelist = new IncrementalLoadingCollection<FileItem>((uint)_currentfiles.Count, (startIndex, Count) =>
            {
                SetLoadingState(true);
                if (_currentfiles == null || _currentfiles.Count <= startIndex) return null;
                List<FileItem> list = new List<FileItem>();
                foreach (var file in _currentfiles.Skip(startIndex).Take(Count))
                {
                    FileItem item = new FileItem();
                    item.File = file;
                    item.Name = file.DisplayName;
                    if (folder.Name.EndsWith("zhx")) item.Name = "zhx" + item.Name;
                    list.Add(item);
                }
                SetLoadingState(false);
                return list;
            }, (o) =>
            {
                o.Dispose();
                return o;
            });


 

 

当增量滚动时 我会针对 触发增量加载事件同时 记录 需要虚化的offset 在回滚的同时 进行释放操作,以保证内存在较低的负载。

在超宽屏幕上的使用效果, 载入了大量本地图片

 

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值