当前位置: 移动技术网 > IT编程>开发语言>c# > ListView用法中与滚动相关的需求实现

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

2019年07月18日  | 移动技术网IT编程  | 我要评论
在 app 的开发过程中,listview 控件是比较常用的控件之一。掌握它的用法,能帮助我们在一定程度上提高开发效率。本文将会介绍 listview 的一种用法——获取并

在 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

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

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网