ListBox 滑动到底部自动加载数据

时间:2021-01-25 19:31:59


概述

在Windows Phone开发应用程序的过程中,常常使用ListBox来显示列表数据。但是有的时候从数据源获取过来的数据量过大,而且用户有可能关心的只是前面几条数据。在往深入了想,甚至完全不必要每次都把大量数据齐刷刷的全部从服务器端获取到客户端来,无论是从流量还是效率上面的考虑都不该。这个时候,从服务器获取数据需要分页,在客户端显示数据也需要在必要的时候才去显示另一页的数据。

联系到使用ListBox来显示数据,我们可能希望初始状态下显示少量数据,当ListBox滑动到最低端的时候,或者当ListBox滑动到最顶端的位置的时候,才去加载更多数据。

 

原理

         ListBox中有一个ScrollViewer,所以才能上下滑动的去查看ListBox中的数据。首先,获取到这个ScrollViewer的状态;然后在ScrollViewer状态改变的时候,依据ScrollViewerExtentHeightVerticalOffsetViewportHeight这三个属性值,判断是否已经到达ListBox的顶端或者底端。

ExtentHeight,获取ScrollViewer中所有内容的垂直大小;VerticalOffset,滚动内容的垂直偏移量;ViewportHeight,可见内容的垂直大小。如果VerticalOffset的值为0,则表示滚动到了ListBox的顶端。如果ExtentHeight-VerticalOffset<=ViewportHeight,表示滚动到了ListBox的底端。

实现

获取ListBox中的ScrollViewer

        /// <summary>
        /// 查找容器内的所有元素,并返回第一个是T类型的元素。
        /// </summary>
        /// <typeparam name="T">返回元素类型</typeparam>
        /// <param name="container">容器</param>
        /// <returns>第一个是T类型的元素</returns>
        private T FindVisualElement<T>(DependencyObject container) where T : DependencyObject
        {
            var childQueue = new Queue<DependencyObject>();

            childQueue.Enqueue(container);

            while (childQueue.Count > 0)
            {
                var current = childQueue.Dequeue();
                T result = current as T;
                if (result != null && result != container)
                {
                    return result;
                }

                int childCount = VisualTreeHelper.GetChildrenCount(current);

                for (int childIndex = 0; childIndex < childCount; childIndex++)
                {
                    childQueue.Enqueue(VisualTreeHelper.GetChild(current, childIndex));
                }
            }

            return null;
        }

获取ScrollViewer的状态

        /// <summary>
        /// 获取指定元素的指定视图状态组。
        /// </summary>
        /// <param name="element">目标元素</param>
        /// <param name="name">视图状态名</param>
        /// <returns>视图状态组</returns>
        private VisualStateGroup FindVisualState(FrameworkElement element, string name)
        {
            if (element == null)
            {
                return null;
            }

            var groups = VisualStateManager.GetVisualStateGroups(element);
            foreach (VisualStateGroup group in groups)
            {
                if (group.Name == name)
                {
                    return group;
                }
            }

            return null;
        }

ScrollViewer状态改变事件

        void visualStateGroup_CurrentStateChanged(object sender, VisualStateChangedEventArgs e)
        {
            var visualState = e.NewState.Name;

            if (visualState == "NotScrolling")
            {
                var v1 = _scrollViewer.ExtentHeight - _scrollViewer.VerticalOffset;
                var v2 = _scrollViewer.ViewportHeight;

                if (v1 <= v2)
                {
                    // TODO: 在此处加载新数据
                }
            }
        }

页面的Loaded事件

            _scrollViewer = FindVisualElement<ScrollViewer>(lstBoxOne);

            if (_isHookedScrollEvent)
            {
                return;
            }

            if (_scrollViewer != null)
            {
                _isHookedScrollEvent = true;

                FrameworkElement element = VisualTreeHelper.GetChild(_scrollViewer, 0) as FrameworkElement;
                if (element != null)
                {
                    VisualStateGroup visualStateGroup = FindVisualState(element, "ScrollStates");
                    visualStateGroup.CurrentStateChanged += new EventHandler<VisualStateChangedEventArgs>(visualStateGroup_CurrentStateChanged);

                }
            }

 

备注:此实例只实现了在ListBox底端刷新数据。若要实现向上滚动到顶端刷新数据,只需要按照原理中的描述,修改ScrollViewer状态改变事件中的逻辑就可以了。