ListView用法中与滚动相关的需求实现

发布时间 - 2026-01-11 02:00:34    点击率:

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

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

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

  • 获取、设置 ListView 的滚动位置;
  • 获取 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 滚动位置处的项目

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

  • ListView 的模板 (Template) 中包括 ScrollViewer,我们可以通过 VisualTreeHelper 获取到此控件;
  • ListView 提供 ContainerFromItem 方法,它使们可以通过传递 Item 获取包括此 Item 的 Container,即 ListViewItem;
  • 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 包括以下两个重要的方法:

  • TransformPoint, 可以将得到的转换信息计算成 Point 值,表示某控件相对于另一控件的坐标位置
  • 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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


# UWP  # ListView  # 滚动  # Android SQLite事务处理结合Listview列表显示功能示例  # Android 实现ListView的点击变色的实例  # Android Adapter里面嵌套ListView实例详解  # Android控件ListView使用方法详解  # Android使用ListView实现滚轮的动画效果实例  # Android实现读取SD卡下所有TXT文件名并用listView显示出来的方法  # Android ListView之EfficientAdapte的使用详解  # 相对于  # 来实现  # 则是  # 这两个  # 遍历  # 所占  # 过程中  # 主要是  # 的是  # 如果你  # 是在  # 如果您  # 我在  # 我会  # 第一个  # 一句  # 将会  # 在此  # 不太  # 列表中 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: 如何在景安云服务器上绑定域名并配置虚拟主机?  HTML透明颜色代码在Angular里怎么设置_Angular透明颜色使用指南【详解】  Laravel怎么自定义错误页面_Laravel修改404和500页面模板  Laravel如何创建自定义中间件?(Middleware代码示例)  在线制作视频的网站有哪些,电脑如何制作视频短片?  Windows驱动无法加载错误解决方法_驱动签名验证失败处理步骤  如何在万网ECS上快速搭建专属网站?  如何确保FTP站点访问权限与数据传输安全?  如何快速生成凡客建站的专业级图册?  香港服务器建站指南:免备案优势与SEO优化技巧全解析  北京网页设计制作网站有哪些,继续教育自动播放怎么设置?  Laravel怎么使用Intervention Image库处理图片上传和缩放  Laravel如何使用withoutEvents方法临时禁用模型事件  js代码实现下拉菜单【推荐】  米侠浏览器网页图片不显示怎么办 米侠图片加载修复  如何实现建站之星域名转发设置?  jquery插件bootstrapValidator表单验证详解  高防服务器如何保障网站安全无虞?  Laravel如何实现多语言支持_Laravel本地化与国际化(i18n)配置教程  Windows10如何更改计算机工作组_Win10系统属性修改Workgroup  Win11怎么关闭专注助手 Win11关闭免打扰模式设置【操作】  Laravel安装步骤详细教程_Laravel环境搭建指南  Laravel的契約(Contracts)是什么_深入理解Laravel Contracts与依赖倒置  Edge浏览器怎么启用睡眠标签页_节省电脑内存占用优化技巧  Android滚轮选择时间控件使用详解  油猴 教程,油猴搜脚本为什么会网页无法显示?  WEB开发之注册页面验证码倒计时代码的实现  JavaScript如何实现音频处理_Web Audio API如何工作?  消息称 OpenAI 正研发的神秘硬件设备或为智能笔,富士康代工  如何实现javascript表单验证_正则表达式有哪些实用技巧  Laravel Eloquent:优雅地将关联模型字段扁平化到主模型中  香港服务器网站生成指南:免费资源整合与高速稳定配置方案  Laravel如何设置定时任务(Cron Job)_Laravel调度器与任务计划配置  Claude怎样写结构化提示词_Claude结构化提示词写法【教程】  如何基于云服务器快速搭建网站及云盘系统?  Laravel如何保护应用免受CSRF攻击?(原理和示例)  html文件怎么打开证书错误_https协议的html打开提示不安全【指南】  SQL查询语句优化的实用方法总结  javascript中的try catch异常捕获机制用法分析  如何生成腾讯云建站专用兑换码?  Chrome浏览器标签页分组怎么用_谷歌浏览器整理标签页技巧【效率】  Gemini手机端怎么发图片_Gemini手机端发图方法【步骤】  Laravel如何使用.env文件管理环境变量?(最佳实践)  Laravel怎么创建自己的包(Package)_Laravel扩展包开发入门到发布  做企业网站制作流程,企业网站制作基本流程有哪些?  制作旅游网站html,怎样注册旅游网站?  CSS3怎么给轮播图加过渡动画_transition加transform实现【技巧】  深圳网站制作公司好吗,在深圳找工作哪个网站最好啊?  php json中文编码为null的解决办法  如何在服务器上配置二级域名建站?