新型应用程序

有关 WinJS ListView 控件的所有必备知识

Rachel Appel

您有数据,很多数据。您需要以如下方式呈现这些数据,即用户可以毫不费力地在应用程序中访问和理解数据。应用程序以新闻文章、配方、赛事比分、财务图表等形式公开其数据,所有数据都呈现在屏幕上大小不同的区域中,尽量吸引使用者的注意。由于小型到中型数据网格便于人们使用、搜索和筛选,现在市场上众多应用程序大都以较为合理的网格或列表格式呈现数据。无论是企业应用程序、个人应用程序还是其他应用程序,网格都是支撑数据快速浏览的基本框架。

在 Windows 应用商店的应用中,通过使用 ListView 控件可以设置这种数据呈现结构。如果您是开发 Windows 应用商店的应用的新手,可以阅读我 2013 年 2 月的文章“使用 HTML5 和 JavaScript 创建 Windows 应用商店的应用”(msdn.microsoft.com/magazine/jj891058) 和我 2013 年 7 月的文章“精通用 JavaScript 构建的 Windows 应用商店应用中的控件和设置”(msdn.microsoft.com/magazine/dn296546),了解相关信息。

ListView 控件基础知识

HTML 和 XAML 中都提供了 ListView 控件,该控件以网格或列表格式呈现数据。在 Windows JavaScript 库 (WinJS) 应用程序(本文的重点)中,通过将主机 <div> 元素上的 data-win-control 属性设置为“WinJS.UI.ListView”,可以使用 ListView 控件,如下所示:

<div id="listView" data-win-control= "WinJS.UI.ListView"></div>

承载 ListView 的 <div> 不包含任何子元素。 不过,它在一个名为 data-win-options 的属性中包含基本配置信息。 通过 data-win-options,可以使用声明性语法在 HTML 页面中设置 ListView 控件的任何属性。 要正确使用 ListView,需要向它应用以下特征:

  • ListView 的组和项模板。
  • ListView 的组和项数据源。
  • ListView 是使用网格还是使用列表布局(默认为网格)。

此外,还应指定 ListView 的项选择模式是单个还是多个(默认为多个)。 在 data-win-options 属性中设置了布局和 selectionMode 属性的基本 ListView 如下所示:

<div id="listView" data-win-control= "WinJS.UI.ListView" data-win-options=
  "{ selectionMode: 'single', layout : {type: WinJS.UI.GridLayout} }" ></div>

尽管上面的代码定义了一个 ListView,但是该 ListView 光靠自身是无法工作的。 它需要借助于 WinJS.Binding.List 对象。 List 对象将填充有对象的数组绑定到项和组模板中定义的 HTML 元素。 这意味着 List 对象定义要显示的数据,模板定义数据的显示方式。

创建 ListView 模板

为 ListView 设置 <div> 后,接下来可以为其创建模板。 ListView 依赖于 HTML 模板来显示用户可读的数据。 幸运的是,Grid、Split 和 Hub(Windows 8.1 中提供了 Hub)Windows 应用商店应用程序模板包含以网格或列表格式呈现数据所需的一切,包括示例数据、预定义 ListView 控件和预定义 CSS 类。 您可以修改这些模板,也可以根据需要创建自己的模板。 但是请注意,如果您创建自己的模板,应遵循新型 UI 设计原则,按 bit.ly/IkosnL 的 Windows 应用商店的应用开发中心所述实现 Windows 8 轮廓。 在您使用内置 Visual Studio 模板时,这一工作已为您完成。

ListView 需要一个项模板,如果要对数据分组,则还需要一个页眉模板。 项和组模板的父元素是 data-win-control 属性已设置为“WinJS.Binding.Template”的简单 <div> 元素。

页眉模板应包含每个组的链接,用户单击这些链接,则会转到列出属于该组的项的页面。 下面是十分常见的母版/详细信息导航模式的示例。 在图 1 中,分类为“headertemplate”的 <div> 包含一个与该组的键绑定的 <button> 元素。 当用户点击或单击该按钮时,会转到一个显示该组成员的页面。

图 1 ListView 控件的页眉和项模板

    <div class="headertemplate" data-win-control="WinJS.Binding.Template">
      <button class="group-header win-type-x-large win-type-interactive"
         data-win-bind="groupKey: key" 
         onclick="Application.navigator.pageControl
        .navigateToGroup(event.srcElement.groupKey)" 
         role="link" tabindex="-1"
         type="button">
        <span class="group-title win-type-ellipsis" data-win-bind=
          "textContent: title"></span>
        <span class="group-chevron"></span>
      </button>
    </div>
    <div class="itemtemplate" data-win-control="WinJS.Binding.Template">
      <div class="item">
        <img class="item-image" src="#" data-win-bind=
          "src: backgroundImage; alt: title" />
          <div class="item-overlay">
            <h4 class="item-title" data-win-bind="textContent: title"></h4>
            <h6 class="item-subtitle win-type-ellipsis" data-win-bind=
              "textContent: subtitle"></h6>
          </div>
      </div>
    </div>

图 1 中的项模板由一些 <div> 标记组成,这些标记中有一个图像和两个文本字段。 现在,新型应用程序中的大部分数据都是图形,因此,项模板内有一个 <img> 元素。 示例数据用一个图像(纯灰色)填充此元素。 图 2 是默认的网格布局 ListView。

The Default ListView from the Grid Template, with Heading and Navigation Buttons Marked in Red
图 2 网格模板的默认 ListView,带标为红色的标题和导航按钮

在对项和组模板编码之后,可以将这些模板与某些数据连接。

ListView 控件的数据和数据绑定

JavaScript 和 JSON 密切相关(毕竟,JSON 是 JavaScript Object Notation),因此,在检索一些 JSON 数据后,只需将数据填充到数组中,Windows.Binding.List 对象会将其转换为 ListView 的可用数据源。 换言之,您不是将 ListView 直接绑定到数组或数据源,这意味着 List 对象用作 ListView 与数据源之间的中介。 这是因为,List 对象将数据转换为 ListView 知道如何使用的内容,而 ListView 本身仅定义网格的外观和布局。 List 对象还提供用于搜索、排序、添加和删除基础数组的成员的方法。

查看 \js\data.js 文件可以看到 Data 命名空间以及构成示例数据的数组。 重要的是,两个数组会合并,形成母版/详细信息关系。 sampleItems 数组(详细信息)中每个对象的组属性都在 sampleGroups 数组(母版)中引用它所在的组,如图 3 所示。

图 3 网格项目模板中数组形式的示例数据

var sampleGroups = [
  { key: "group1", title: "Group Title: 1", 
     subtitle: "Group Subtitle: 1",
     backgroundImage: darkGray, description: groupDescription },
  { key: "group2", title: "Group Title: 2", 
     subtitle: "Group Subtitle: 2",
     backgroundImage: lightGray, description: groupDescription },
  { key: "group3", title: "Group Title: 3", 
     subtitle: "Group Subtitle: 3",
     backgroundImage: mediumGray, description: groupDescription }
];
var sampleItems = [
  { group: sampleGroups[0], title: "Item Title: 1",
    subtitle: "Item Subtitle: 1", 
    description: itemDescription,
     content: itemContent, backgroundImage: lightGray },
  { group: sampleGroups[0], title: "Item Title: 2",
     subtitle: "Item Subtitle: 2", 
     description: itemDescription,
     content: itemContent, backgroundImage: darkGray },
  { group: sampleGroups[0], title: "Item Title: 3", subtitle:
     "Item Subtitle: 3", 
     description: itemDescription,
     content: itemContent, backgroundImage: mediumGray },
  { group: sampleGroups[1], title: "Item Title: 1", subtitle:
     "Item Subtitle: 1", description: itemDescription,
     content: itemContent, backgroundImage: darkGray },
  { group: sampleGroups[2], title: "Item Title: 2", subtitle:
     "Item Subtitle: 2", description: itemDescription,
     content: itemContent, backgroundImage: mediumGray },
];

当然,您将通过生成自己的数组、访问 JSON 或 XML 数据或者调用 Web 服务,用自己的数据来替换示例数据。 您不是非得使用 Data 命名空间,您可以定义自己的命名空间。

靠近 data.js 文件顶部的位置是以下代码行:

var list = new WinJS.Binding.List();

此列表变量是一个数组容器。 您可通过将数组传递到 List 的构造函数方法中或通过使用推送方法向 List 添加数组:

generateSampleData().forEach(function (item) {
  list.push(item);
});

执行此操作会用数组数据填充列表,现在可将列表与 ListView 进行关联。 如果要使用默认模板代码,则应在应用程序第一次加载时,在 \js\default.js 文件的 _initializeLayout 函数中执行此关联,如下所示:

listView.itemDataSource = Data.items.dataSource;
listView.groupDataSource = Data.groups.dataSource;

当然,根据数据的大小,加载时间可能不同,因此,您可能需要修改加载过程。 请尽量判断好数据加载到内存的实际情况,注意性能对用户的重要性。

请注意,项和组数据源分别设置为 Data.items.dataSource 和 Data.groups.dataSource。 Data 命名空间的“items”和“groups”成员引用的是创建了包含该数据的数组(即 groupedItems)的函数。 \js\data.js 文件中的 Data 命名空间声明反映了这个概念,并且包含命名空间中用于处理数据的其他公共成员:

WinJS.Namespace.define("Data", {
  items: groupedItems,
  groups: groupedItems.groups,
  getItemReference: getItemReference,
  getItemsFromGroup: getItemsFromGroup,
  resolveGroupReference: resolveGroupReference,
  resolveItemReference: resolveItemReference
});

Data 命名空间的项和组成员指向正确构造了数据的 groupedItems 对象。 到现在为止,Data 命名空间中的所有内容都包括在 Visual Studio 项目模板中。 如果选择从空白项目开始,则需要通过创建类似的数据访问方法(而不是依赖于 Data 命名空间成员)来创建数据。

此时,ListView 已完成,已设置好数据和绑定。 通过使用 data-win-bind 属性可以将数据源中对象的属性绑定到 HTML 元素,如下所示:

<h4 class="item-title" data-win-bind="textContent: title"></h4>

上述代码行将 title 属性绑定到 <h4> 元素,作为其文本的一部分。 图 1图 2 显示了更多 data-win-bind 属性的实际示例。

现在,ListView 和数据访问已准备就绪,此时可以继续设置 ListView 样式。

设置 ListView 控件的样式

演示内容全部与样式有关。 WinJS 库包含一组完整的 CSS 规则,其中有预定义的样式,您可以覆盖这些样式,通过各种方式创建 ListView。 如果不熟悉 WinJS 控件的样式设置,请参阅我 2013 年 10 月的文章“使用适用于 WinJS 应用程序的 CSS 构建响应迅速的现代 UI”msdn.microsoft.com/magazine/dn451447。 您可通过覆盖 .win-listview CSS 类来设置整个 ListView 的样式。 在设置 ListView 的样式时,您可以使用以下类选择器来设置 ListView 的构成部分:

  • .win-viewport:设置 ListView 视区的样式。 viewport 是滚动条所在的位置。
  • .win-surface:设置 ListView 可滚动区域的样式。 此区域会在用户执行滚动时移动。

对 ListView 中的项设置样式的方法有两种。 您可通过 .win-item 类将样式应用于项模板,也可以重写 .win-container 类。 请注意,ListView 中的每一项都包含多个 HTML 元素(请参阅图 1 查看这些元素)。 如图 1 所示,构成项模板的 <div> 元素包含 .item、.item-image、.item-overlay、.item-title 和 .item-subtitle 类。 系统样式表(即 ui-light 和 ui-dark)中对这些类没有任何定义,因为它们需要由您设置样式。

请注意样式设置所涉及的一些陷阱,尤其是在何时将边距和填充应用于 ListView 控件方面。 您可以在 bit.ly/HopfUg 的 Windows 应用商店的应用开发中心查看 ListView 样式设置的全部相关信息。 不要忘记为应用程序可能需要的所有视图状态创建样式。

Windows 8.1 包含 ListView 的一些样式更改,这些更改与子代/后代选择器特定性有关。 这是因为,新节点属于页面的内部树结构,因此,必须更新 CSS 选择器才能包含 .win-itembox 类选择器,如下所示:.win-container | .win-itembox | .win-item

响应 ListView 中的视图状态更改

要通过 Windows 应用商店认证过程,响应新的 Windows 8 对齐视图和填充视图非常重要。 对齐视图,以及完整视图和填充视图,可以让用户在屏幕上排列多个 Windows 应用商店的应用。 在 Windows 8 中,用户可以调整大小以打开最多两个应用程序窗口,一个为填充视图,另一个为对齐视图。 在 Windows 8.1 中,最大窗口数增加为 4,有更多应用程序显示选项。 在 Windows 8.1 中,这些视图称为高视图或窄视图,与 Windows 8 的对齐视图和填充视图略有不同。

这意味着您必须对 ListView 控件进行编码,根据应用程序视图状态的变化更改其格式。 此过程称为响应式设计,可通过使用 CSS 媒体查询实现此过程。 有关 CSS 媒体查询的入门指导,请参阅我的博客文章“使用 CSS 媒体查询创建移动站点布局”bit.ly/1c39mDx

媒体查询设置 ListView 控件的形状,用一种对显示每个视图时的不同屏幕尺寸和方向都有意义的方式来适合不同视图状态。 在切换到高视图时,ListView 需要转变为列表,如下所示:

listView.layout = new ui.ListLayout();

之后,当用户切换回原始视图状态时,必须将 ListView 设置回网格:

listView.layout = new ui.GridLayout({ groupHeaderPosition: "top" });

如果要更改在屏幕发生变化时 ListView 中各项的样式,请在 \css\default.css 文件中向此媒体查询添加 CSS:

@media screen and (-ms-view-state: snapped) {...}

您不需要针对完整或填充视图进行媒体查询,因为这些视图使用默认样式表。 不过,您可以根据需要,针对各种屏幕尺寸使用不同的媒体查询。

ListView 和语义缩放

Windows 8 的重塑功能包含一些用于可视化、导航和搜索数据的新方法。 这意味着您需要用不同的方式考虑如何进行搜索。 用户现在不必在搜索框中键入短语并在列表中筛选,而是可以使用语义缩放将数据压缩为可理解的多个部分。

通过语义缩放,用户可使用收缩手势(或 Ctrl + 鼠标滚轮)进行平移或缩小,以聚合格式观察数据。 例如,Windows“开始”页面在用户执行缩小时向用户显示所有可用应用程序,就表现出这种行为。 在应用程序中使用语义缩放十分方便,因为它就是 WinJS 的控件:

<div data-win-control="WinJS.UI.SemanticZoom">
  <!-- The control that provides the zoomed-in view goes here. -->
  <!-- The control that provides the zoomed-out view goes here. -->
</div>

SemanticZoom 控件是一个或两个 ListView 的包装,也可能是 Windows 8.1 新增的 HTML Repeater 控件。

有关 ListView 的其他注意事项

请勿将 ListView 用作通用布局控件。 CSS 框模型才用作通用布局控件。 在 Windows 8.1 中,应考虑适合使用 ListView 控件还是 Repeater 控件。 如果不需要控件的很多功能,只需多次重复相同的 HTML 设计,则更适合用 Repeater 控件。 在撰写本文时,Windows 8.1 还是预览版,因此,可能会对 ListView 以及其他 Windows 应用商店的应用 API 组件进行一些其他更改。 有关 Windows 8.1 API 更改的详细信息,请参阅 bit.ly/1dYTylx 的开发中心文档。

Rachel Appel是一名顾问、作家、导师和前 Microsoft 员工,在 IT 行业有 20 多年的经验。她常在 Visual Studio Live!、DevConnections、MIX 等顶级行业大会上发言。她的专业是开发侧重于 Microsoft 系列开发技术和开放式 Web 并且符合业务和技术需要的解决方案。有关 Appel 的详细信息,请访问她的网站 rachelappel.com

衷心感谢以下技术专家对本文的审阅:Eric Schmidt (Microsoft)
Eric Schmidt 是 Microsoft 的 Windows 开发者内容团队的内容开发者,负责撰写有关 Windows JavaScript 库 (WinJS) 的文章。 以前,他在 Microsoft Office 部门为 Office 平台的应用程序编写代码示例。 工作之余,他与家人一起,弹奏弦贝司,制作 HTML5 视频游戏,或者撰写有关塑料积木玩具的博客文章 (historybricks.com)。