在 App 的开发过程中,ListView 控件是比较常用的控件之一。掌握它的用法,能帮助我们在一定程度上提高开发效率。本文将会介绍 ListView 的一种用法——获取并设置 ListView 的滚动位置,以及获取滚动位置处的项目。这里多说一句,由于这个描述有点,所以本文的标题实在不好起。

举个例子,如果你正在开发的应用有这样一个需求,当用户从一个列表页(包括 ListView 控件)返回到前一页面时,你需要得到用户在浏览 ListView 中的内容到哪个位置以及哪一项了,以便告诉用户最近浏览项,并且可以让用户再次打开列表时,直接从上次浏览的位置处继续浏览。如下图:

本文介绍了实现上述需求的方法。具体来说,这个需求可细分为两个小需求,即:

  1. 获取、设置 ListView 的滚动位置;
  2. 获取 ListView 滚动位置处的项目。

以下我会通过上面配图中的 Demo 应用逐一说明(本文末尾有源码下载链接),这个 Demo 包括两个页面,一个主页 (MainPage),一个列表页 (ItemsPage)。主页中包括:

  • 按钮:可以导航到 ItemsPage;
  • 最近浏览信息区域:可以查看上次浏览的项目,并提供一个按钮可以导航到列表页中上次浏览的项目处;

而列表页,则包括一个 ListView 控件,展示若干个项目。

一、获取、设置 ListView 的滚动位置

关于获取、设置 ListView 的滚动位置,微软已经提供了相关的例子,我在这个 Demo 中是直接套用的。这个功能主要是通过 ListViewPersistenceHelper 来实现的,它提供以下两个方法:

       // 获取 ListView 的滚动位置public static string GetRelativeScrollPosition(ListViewBase listViewBase, ListViewItemToKeyHandler itemToKeyHandler)// 设置 ListView 的滚动位置public static IAsyncAction SetRelativeScrollPositionAsync(ListViewBase listViewBase, String relativeScrollPosition, ListViewKeyToItemHandler keyToItemHandler)

这两个方法中各有一个参考是委托类型,分别是 ListViewItemToKeyHandler 和 ListViewKeyToItemHandler,它们的作用是告诉这个类如何处理列表项与 Key 的对应关系,好使得该类可以正确地获取或设置滚动位置。这里的 Key 是 ListViewItem 所代表的项目的一个属性(比如 Demo 中 Item 类的 Id 属性),这个属性的值在整个列表中是唯一的;而 Item 是在 Item 对象本身。在 Demo 中它们的实现分别如下:

        private string ItemToKeyHandler(object item){Item dataItem = item as Item;if (dataItem == null) return null;return dataItem.Id.ToString();}private IAsyncOperation<object> KeyToItemHandler(string key){Func<System.Threading.CancellationToken, Task<object>> taskProvider = token =>{var items = listView.ItemsSource as List<Item>;if (items != null){var targetItem = items.FirstOrDefault(m => m.Id == int.Parse(key));return Task.FromResult((object)targetItem);}else{return Task.FromResult((object)null);}};return AsyncInfo.Run(taskProvider);}

实现这两个方法后,重载列表页的  OnNavigatingFrom 方法,在其中加入以下代码,来实现获取滚动位置并保存:

           string position = ListViewPersistenceHelper.GetRelativeScrollPosition(this.listView, ItemToKeyHandler);NavigationInfoHelper.SetInfo(targetItem, position);

继续为页面注册 Loaded 事件,在 Loaded 事件中加入以下代码来实现设置滚动位置:

            if (navigationParameter != null){if (NavigationInfoHelper.IsHasInfo){await ListViewPersistenceHelper.SetRelativeScrollPositionAsync(listView, NavigationInfoHelper.LastPosition, KeyToItemHandler);}}

这里需要注意的是,设置滚动位置的方法是异步的,所以 Loaded 方法需要加上 async 修饰符。而上述代码中对 navigationParameter 参数的判断则是为了区别:在导航时是否定位到最近浏览的位置,具体可参考 Demo 的代码。

二、获取 ListView 滚动位置处的项目

关于第二个需求的实现,我们首先需要明白以下三点:

  1. ListView 的模板 (Template) 中包括 ScrollViewer,我们可以通过 VisualTreeHelper 获取到此控件;
  2. ListView 提供 ContainerFromItem 方法,它使们可以通过传递 Item 获取包括此 Item 的 Container,即 ListViewItem;
  3. UIElement 提供 TransformToVisual 方法,可以得到某控件相对指定控件的位置转换信息;

所以我们的思路就是:得到 ListView 控件中的 ScrollViewer,并遍历 ListView 中所有的 Item,在遍历过程中,得到每一项目的 ListViewItem,并判断它的位置是否位于 ScrollViewer 的位置中。以下是获取 ListView 中当前所有可见项的代码:

        public static List<T> GetAllVisibleItems<T>(this ListViewBase listView){var scrollViewer = listView.GetScrollViewer();if (scrollViewer == null){return null;}List<T> targetItems = new List<T>();foreach (T item in listView.Items){var itemContainer = listView.ContainerFromItem(item) as FrameworkElement;bool isVisible = IsVisibileToUser(itemContainer, scrollViewer, true);if (isVisible){targetItems.Add(item);}}return targetItems;}

在上述代码的 foreach 循环中的部分,正是我们前述思路的体现。而其中所调用的 IsVisibleToUser 方法,则是如何判断某一 ListViewItem 是否在 ScrollViewer 中为当前可见。其代码如下:

        /// <summary>/// Code from here:///  https://social.msdn.microsoft.com/Forums/en-US/86ccf7a1-5481-4a59-9db2-34ebc760058a/uwphow-to-get-the-first-visible-group-key-in-the-grouped-listview?forum=wpdevelop/// </summary>/// <param name="element">ListViewItem or element in ListViewItem</param>/// <param name="container">ScrollViewer</param>/// <param name="isTotallyVisible">If the element is partially visible, then include it. The default value is false</param>/// <returns>Get the visibility of the target element</returns>private static bool IsVisibileToUser(FrameworkElement element, FrameworkElement container, bool isTotallyVisible = false){if (element == null || container == null)return false;if (element.Visibility != Visibility.Visible)return false;Rect elementBounds = element.TransformToVisual(container).TransformBounds(new Rect(0.0, 0.0, element.ActualWidth, element.ActualHeight));Rect containerBounds = new Rect(0.0, 0.0, container.ActualWidth, container.ActualHeight);if (!isTotallyVisible){return (elementBounds.Top < containerBounds.Bottom && elementBounds.Bottom > containerBounds.Top);}else{return (elementBounds.Bottom < containerBounds.Bottom && elementBounds.Top > containerBounds.Top);}}

可以看出,我们是能过得到两个 Rect 值。Rect 类型的值代表一个矩形区域的位置和大小,我们对这两个值进行比较后,返回最终的结果。
获取 ListViewItem 的 Rect 值: element.TransformToVisual(container) 返回的结果是 GeneralTransform 类型,这个值表明了 ListViewItem 相对于 Container(即 ScrollViewer)的位置转换信息。GeneralTransform 类型可能我们并不太熟悉,不过,从它派生出来的这些类: ScaleTransform、TranslateTransform ,我们就熟悉了,GeneralTransform 正是它们的基类。GeneralTransform 包括以下两个重要的方法:

  1. TransformPoint, 可以将得到的转换信息计算成 Point 值,表示某控件相对于另一控件的坐标位置
  2. TransformBounds,可以将得到的转换信息计算成 Rect 值,表示某控件相对于另一控件的坐标位置及所占的区域。

所以,我们通过 TransformBounds 方法就得到了 ListViewItem 相对于 ScrollViewer 的位置和所占区域的信息。
获取 ScrollViewer 的 Rect 值: 直接实例化一个 Rect,以 0,0 作为你左上角的坐标位置点, ScrollViewer 的 ActualWidth 和 ActualHeight 作为其大小。

接下来,就是比较的过程:这里,我们做了一个判断,判断是否要求元素 (ListViewItem) 完全在 ScrollViewer 中(而非仅部分在其中)。如果要求部分显示即可,则只要元素的 Top 小于 Container 的 Bottom 值,并且元素的 Bottom 大于 Container 的 Top;如果要求全部显示,那么算法是:元素的 Top 大于 Container 的 Top 并且元素的 Bottom 小于 Container 的 Bottom。如果您对语言描述或者代码都还不明白,也可以在纸上画一下进行比较。

接下来,我们照着 GetAllVisbleItems 方法的思路可以实现 GetFirstVisibleItem 方法,即获取列表中第一个可见项,代码可参考 Demo 的源码,在此不再赘述。

我们在之前重载的方法 OnNavigatingFrom 中加上这句代码,即可以获取到用户浏览位置处的那一项。

          var targetItem = this.listView.GetFirstVisibleItem<Item>();

至此,所有主要功能已经基本完成。

结语

本文介绍了如何获取和设置 ListView 的滚动位置,以及获取滚动位置处的那一项,前者主要是借助于 ListViewPersistenceHelper 来实现,后者则是通过获取 ListViewItem 和 ScrollViewer 的 Rect 值并进行比较而最终实现的。如果您有更好的方法、不同的看见,请留言,共同交流。

源码下载

参考资料:

ListView Sample
How to get the first visible group key in the grouped listview

转载于:https://www.cnblogs.com/wpinfo/p/6437487.html

UWP: ListView 中与滚动有关的两个需求的实现相关推荐

  1. android listview中播放视频,支持全屏

    最近在做项目的时候有需求要在 listView 中播放视频,并且支持横竖屏无缝切换,在网上搜索了一下,关于这种 demo真的很少, 有的也只是实现简单的功能,无法满足项目中的需求,想着修改一下凑合用, ...

  2. excel中使用CORREL函数计算两个时间序列数据列之间的滚动相关性(Rolling correlations)、例如,计算两种商品销售额之间的3个月的滚动相关性

    excel中使用CORREL函数计算两个时间序列数据列之间的滚动相关性(Rolling correlations).例如,计算两种商品销售额之间的3个月的滚动相关性 目录

  3. R语言使用zoo包中的rollapply函数计算两个时间序列数据列之间的滚动相关性(Rolling correlations)、例如,计算两种商品销售额之间的3个月的滚动相关性

    R语言时间序列数据滚动相关性分析(Rolling correlations).R语言使用zoo包中的rollapply函数计算两个时间序列数据列之间的滚动相关性(Rolling correlation ...

  4. PullScrollView详解(六)——延伸拓展(listview中getScrollY()一直等于0、ScrollView中的overScrollBy)

    前言:经常说follow your heart.但等到真到这么一天的时候,却很艰难 相关文章: 1.<PullScrollView详解(一)--自定义控件属性> 2.<PullScr ...

  5. Android将联系人读取到LISTVIEW中遇到的问题!

    最近在学习Android开发,学习到ListView控件使用的时候参考了<Android应用开发揭秘>中的代码,由于该书是基于Android  2.0进行编写的,其中的部分代码到了更新的A ...

  6. Android在ListView中嵌套一个GridView时只显示一行的原因及解决方法

    在之前的编程里,我还没有遇到过要在一个ListView中嵌套一个GridView或是在一个GridView中嵌套一个ListView.所以今天事儿来了!我花了一将近3个小时,找到了为什么我在一个Lis ...

  7. timerpickerview使用_详解iOS App中UIPickerView滚动选择栏的添加方法

    1.UIPickerView的宽度和高度是固定的,纵向是320216,横向是568162 2.属性: @property(nonatomic,readonly)NSInteger numberOfCo ...

  8. Win10 UWP开发中的重复性静态UI绘制小技巧 1

    Win10 UWP开发中的重复性静态UI绘制小技巧 1 原文:Win10 UWP开发中的重复性静态UI绘制小技巧 1 介绍 在Windows 10 UWP界面实现的过程中,有时会遇到一些重复性的.静态 ...

  9. Listview中使用线程实现无限加载更多项目的功能

    相关搜索:"Listview 加载更多", 在现在的SINA微博或者象twitter,dzone等网站中,当加载一个很长的列表时,往往都是 先加载部分内容,然后当用户用拖拉条往下拖 ...

最新文章

  1. python操作word填表_Python 自动化办公—Word 文本操作命令
  2. #region(C# 参考)
  3. 【JAVA秒会技术之秒杀面试官】JavaEE常见面试题(六)
  4. sicily 1024 Magic Island
  5. Nginx-1.9.8推出的切片模块
  6. linux下面把png文件转化为背景透明
  7. 三维重建 几何方法 深度学习_Occupancy Networks:基于学习函数空间的三维重建表示方法...
  8. 让Spring架构减化事务配置(转)
  9. 第一百二十八期:推荐几个IDEA插件,Java开发者撸码利器,你get到了吗
  10. r语言regexpr函数_R语言学习笔记-文本挖掘之字符处理(1)
  11. 程序员,30岁前最好都找大厂,好好做技术
  12. 【PROTEUS】使用PROTEUS与电脑串口调试助手进行通讯
  13. Span 介绍及使用(二)
  14. 短视频系统源码,android 真正的全屏沉浸式体验
  15. 在类方法中可 用this来调用本类的类方法
  16. 巴西龟饲养日志----肺炎治疗情况
  17. java 控制器的类型和作用,跳转页面的方式
  18. MySQL 统计数量的方式 coutn(*) 和 count(1)
  19. 港联证券:为什么会出现股票跌停?股票跌停应该怎么卖出?
  20. 有什么毫不起眼却闷声发大财的行业或者生意?

热门文章

  1. C++用数组和链表分别实现Queue
  2. 在页面中控制媒体流的起播点和播放长度
  3. php拍照从手机相册中选择,微信js-sdk预览图片接口及从拍照或手机相册中选图接口用法示例...
  4. 移动应用程序和网页应用程序_您的移动应用程序运行缓慢的主要原因以及如何修复它...
  5. 如何使用Web Audio API听到“ Yanny”和“ Laurel”的声音
  6. idea使用maven创建java工程log4j的配置
  7. 白盒测试的3中主要方法(cont.)
  8. Fedora 提出统一流程,弃用上千 Python 2 软件包更可控
  9. charles和Fiddler感觉哪个更好用
  10. 20170507Linux七周二次课 io监控free ps 网络状态 抓包