摘要:第 19 章. 集合视图
注意
本书于 2016 年春季出版,之后再未更新。 书中有许多内容仍然有价值,但有些内容已过时,有些主题不再完全正确或完整。
Xamarin.Forms 定义了用于维护集合并显示其元素的三个视图:
Picker
是一个相对较短的字符串项列表,允许用户选择一个项ListView
通常是较长的项列表,这些项通常具有相同的类型和格式,同样允许用户选择一个项TableView
是用于显示数据或管理用户输入的单元格集合(通常为各种类型和外观)
MVVM 应用程序通常使用 ListView
来显示可选择的对象集合。
带有 Picker 的程序选项
当你需要允许用户从相对较短的 string
项列表中选择一个选项时,Picker
是不错的选择。
Picker 和事件处理
PickerDemo 示例演示如何使用 XAML 来设置 Picker
Title
属性,并将 string
项添加到 Items
集合。 当用户选择 Picker
时,它将以与平台相关的方式显示 Items
集合中的项。
SelectedIndexChanged
事件指示用户何时选择了一个项。 然后,从零开始的 SelectedIndex
属性指示选定的项。 如果未选定项,则 SelectedIndex
等于 –1。
还可以使用 SelectedIndex
初始化选定项,但必须在填充 Items
集合后设置该项。 在 XAML 中,这意味着你可能会使用属性元素来设置 SelectedIndex
。
数据绑定 Picker
SelectedIndex
属性受可绑定属性的支持(但 Items
不受支持),因此,对 Picker
使用数据绑定比较困难。 一种解决方法是将 Picker
与 ObjectToIndexConverter
结合使用(如 Xamarin.FormsBook.Toolkit 库中的项)。 PickerBinding 演示了其工作原理。
注意
Xamarin.FormsPicker
现在包含支持数据绑定的 ItemsSource
和 SelectedItem
属性。 请参见 Picker。
使用 ListView 呈现数据
ListView
是派生自 ItemsView<TVisual>
的唯一类,它在其中继承 ItemsSource
和 ItemTemplate
属性。
ItemsSource
类型为 IEnumerable
但默认为 null
,必须通过数据绑定将其显式初始化或设置为集合(更常见)。 此集合中的项可以是任何类型。
ListView
定义一个 SelectedItem
属性,该属性设置为 ItemsSource
集合中的一个项,或者,如果未选择任何项,则设置为 null
。 选择新项时,ListView
将触发 ItemSelected
事件。
集合和选择
ListViewList 示例使用 List<Color>
集合中的 17 Color
值填充 ListView
。 这些项是可选的,但在默认情况下,这些项将与其外观欠佳的 ToString
表示形式一起显示。 本章中的几个示例说明了如何修复此显示,并使其在理想情况下更具吸引力。
行分隔符
在 iOS 和 Android 显示屏上,会有一根细线分隔行。 可以通过 SeparatorVisibility
和 SeparatorColor
属性来控制这种情况。 SeparatorVisibility
属性的类型为 SeparatorVisibility
,这是一个包含两个成员的枚举:
数据绑定选定项
SelectedItem
属性由可绑定属性支持,这意味着它可以是数据绑定的源或目标。 其默认 BindingMode
为 OneWayToSource
,但通常是双向数据绑定的目标,尤其是在 MVVM 方案中。 ListViewArray 示例演示了这种类型的绑定。
ObservableCollection 差异
ListViewLogger 示例将 ListView
的 ItemsSource
属性设置为 List<DateTime>
集合,然后使用计时器每隔一秒逐步向集合中添加一个新的 DateTime
对象。
不过,ListView
不会自动更新自身,因为 List<T>
集合没有指示何时在集合中添加或移除项的通知机制。
在此类方案中,一个更好的类是 System.Collections.ObjectModel
命名空间中定义的 ObservableCollection<T>
。 此类实现 INotifyCollectionChanged
接口,因此当在集合中添加或移除项时,或者当在集合中替换或移动项时,将触发 CollectionChanged
事件。 当 ListView
内部检测到实现 INotifyCollectionChanged
的类已设置为其 ItemsSource
属性时,它会将一个处理程序附加到 CollectionChanged
事件,并在集合更改时更新其显示。
ObservableLogger 示例演示了 ObservableCollection
的用法。
模板和单元格
默认情况下,ListView
使用每个项的 ToString
方法显示其集合中的项。 更好的方法包括定义用于显示项的模板。
若要试验此功能,可以使用 Xamarin.FormsBook.Toolkit 库中的 NamedColor
类。 此类定义 IList<NamedColor>
类型的静态 All
属性,该属性包含与 Color
结构的公共字段对应的 141 个 NamedColor
对象。
NaiveNamedColorList 示例将 ListView
的 ItemsSource
设置为此 NamedColor.All
属性,但只显示 NamedColor
对象的完全限定类名。
ListView
需要一个模板来显示这些项。 在代码中,可以使用引用 Cell
类的派生的 DataTemplate
构造函数 将 ItemsView<TVisual>
定义的 ItemTemplate
属性设置为 DataTemplate
对象。 Cell
有五个导数:
TextCell
- 包含两个Label
视图(从概念上讲)ImageCell
- 向TextCell
添加Image
视图EntryCell
- 包含一个Entry
视图,其中包含Label
SwitchCell
- 包含带Label
的Switch
ViewCell
- 可以是任何View
(可能具有子元素)
然后,对 DataTemplate
对象调用 SetValue
和 SetBinding
,以将值与 Cell
属性相关联,或在引用 ItemsSource
集合中的项属性的 Cell
集合上设置数据绑定。 TextCellListCode 示例对此进行了演示。
当 ListView
显示每一项时,将从模板构造一个小型可视化树,并在此可视化树中的元素的项和属性之间建立数据绑定。 可以通过以下方式了解此过程:安装适用于 ListView
的 ItemAppearing
和 ItemDisappearing
事件的处理程序,或使用替代 DataTemplate
构造函数 ,该构造函数使用一个函数,在每次必须创建一个项的可视树时调用该函数。
TextCellListXaml 在 XAML 中显示一个功能完全相同的程序。 DataTemplate
标记设置为 ListView
的 ItemTemplate
属性,然后 TextCell
设置为 DataTemplate
。 绑定到集合中的项的属性将直接在 TextCell
的 Text
和 Detail
属性上设置。
自定义单元格
在 XAML 中,可以将 ViewCell
设置为 DataTemplate
,然后将自定义可视化树定义为 ViewCell
的 View
属性。 (View
是 ViewCell
的内容属性,因此不需要 ViewCell.View
标记。)CustomNamedColorList 示例演示了此方法:
为所有平台获取大小调整权限可能比较棘手。 RowHeight
属性很有用,但在某些情况下,你将需要改用 HasUnevenRows
属性,此方法效率较低,但会强制 ListView
调整行的大小。 对于 iOS 和 Android,必须使用这两个属性中的一个才能获得正确的行大小。
将 ListView 项分组
ListView
支持项分组,并在这些组之间导航。 ItemsSource
属性必须设置为集合的集合:ItemsSource
设置的目标对象必须实现 IEnumerable
,并且集合中的每个项还必须实现 IEnumerable
。 每个组应包括两个属性:组的文本说明和三个字母的缩写。
Xamarin.FormsBook.Toolkit 库中 NamedColorGroup
类创建了七组 NamedColor
对象。 ColorGroupList 示例展示了如何使用这些组,同时将 ListView
的 IsGroupingEnabled
属性设置为 true
,并将 GroupDisplayBinding
和 GroupShortNameBinding
和属性绑定到每个组中的属性。
自定义组标头
可以通过将 GroupDisplayBinding
属性替换为定义标头模板的 GroupHeaderTemplate
,为 ListView
组创建自定义标头。
ListView 和交互性
通常,应用程序通过将处理程序附加到 ItemSelected
或 ItemTapped
事件或通过在 SelectedItem
属性上设置数据绑定来获得用户与 ListView
的交互。 但某些单元格类型(EntryCell
和 SwitchCell
)允许用户交互,还可以创建自行与用户交互的自定义单元格。 InteractiveListView 创建了 100 个 ColorViewModel
实例,允许用户使用三个 Slider
元素更改每种颜色。 该程序还利用 Xamarin.FormsBook.Toolkit 中的 ColorToContrastColorConverter
。
ListView 和 MVVM
ListView
在 MVVM 方案中发挥着重要作用。 每当 ViewModel 中存在 IEnumerable
集合时,它通常绑定到 ListView
。 此外,集合中的项通常实现 INotifyPropertyChanged
以便与模板中的属性绑定。
ViewModel 集合
为了探索这一点,SchoolOfFineArts 库将基于 XML 数据文件和此虚构学校的虚构学生映像创建多个类。
Student
类从 ViewModelBase
派生。 StudentBody
类是 Student
对象的集合,同样派生自 ViewModelBase
。 SchoolViewModel
下载 XML 文件并汇编所有对象。
StudentList 程序使用 ImageCell
在 ListView
中显示学生及其映像:
ListViewHeader 示例添加 Header
属性,但它仅在 Android 上显示。
选择和绑定上下文
SelectedStudentDetail 程序将 StackLayout
的 BindingContext
绑定到 ListView
的 SelectedItem
属性。 这允许程序显示有关所选学生的详细信息。
上下文菜单
单元格可以定义以特定于平台的方式实现的上下文菜单。 若要创建此菜单,请将 MenuItem
对象添加到 Cell
的 ContextActions
属性。
MenuItem
定义五个属性:
string
类型的Text
FileImageSource
类型的Icon
bool
类型的IsDestructive
ICommand
类型的Command
object
类型的CommandParameter
Command
和 CommandParameter
属性表示每一项的 ViewModel 包含用于执行所需菜单命令的方法。 在非 MVVM 方案中,MenuItem
还定义 Clicked
事件。
CellContextMenu 演示了此方法。 每个 MenuItem
的 Command
属性都绑定到 Student
类中 ICommand
类型的属性。 将 IsDestructive
属性设置为移除或删除所选对象的 MenuItem
的 true
。
改变视觉对象
有时,你可能希望根据属性对 ListView
中的项的视觉对象稍作更改。 例如,当一名学生的平均积分点低于 2.0 时,ColorCodedStudents 示例会将该学生姓名显示为红色。
这是通过使用 Xamarin.FormsBook.Toolkit 库中的绑定值转换器 ThresholdToObjectConverter
来实现的。
刷新内容
ListView
支持用于刷新其数据的下拉手势。 要启用此设置,程序必须将 IsPullToRefresh
属性设置为 true
。 ListView
通过将 IsRefreshing
属性设置为 true
,以及通过引发 Refreshing
事件和调用 RefreshCommand
属性的 Execute
方法(面向 MVVM 场景)来响应下拉手势。
然后,处理 Refresh
事件或 RefreshCommand
的代码可能会更新 ListView
显示的数据,并将 IsRefreshing
设置回 false
。
RssFeed 示例演示如何使用实现 RefreshCommand
和 IsRefreshing
属性进行数据绑定的 RssFeedViewModel
。
TableView 及其意向
尽管 ListView
通常显示同一类型的多个实例,但 TableView
通常侧重于为各种类型的多个属性提供用户界面。 每一项都与其自己的 Cell
导数相关联,以显示属性或为其提供用户界面。
属性和层次结构
TableView
仅定义四个属性:
TableIntent
类型的Intent
,一个枚举TableRoot
类型的Root
:TableView
的内容属性int
类型的RowHeight
bool
类型的HasUnevenRows
TableIntent
枚举指示你打算如何使用 TableView
:
这些成员还建议 TableView
的一些用途。
定义表涉及多个其他类:
TableSectionBase
是一个抽象类,它派生自BindableObject
并定义Title
属性TableSectionBase<T>
是一个抽象类,它派生自TableSectionBase
并实现IList<T>
和INotifyCollectionChanged
TableSection
派生自TableSectionBase<Cell>
TableRoot
派生自TableSectionBase<TableSection>
简而言之,TableView
具有设置为 TableRoot
对象的 Root
属性,该对象是 TableSection
对象的集合,其中每个对象都是 Cell
对象的集合。 一个表包含多个部分,每个部分都有多个单元格。 表本身可以有一个标题,且每个部分可以有一个标题。 尽管 TableView
利用 Cell
派生,但它不使用 DataTemplate
。
普通窗体
EntryForm 示例定义了 PersonalInformation
视图模型,该模型的一个实例成为 TableView
的 BindingContext
。 然后,其 TableSection
中的每个 Cell
导数都可以绑定到 PersonalInformation
类的属性。
自定义单元格
ConditionalCells 示例展开 EntryForm。 ProgrammerInformation
类包含一个布尔属性,该属性控制两个附加属性的适用性。 对于这两个附加属性,该程序使用基于 Xamarin.FormsBook.Toolkit 库中的 PickerCell.xaml 和 PickerCell.xaml.cs 的自定义 PickerCell
。
尽管两个 PickerCell
元素的 IsEnabled
属性绑定到 ProgrammerInformation
中的布尔属性,但此方法似乎不起作用,这会提示下一个示例。
条件部分
ConditionalSection 示例将两个对布尔项的选择有条件的项放在单独的 TableSection
中。 代码隐藏文件从 TableView
中移除此部分,或根据布尔属性将其重新添加。
TableView 菜单
TableView
的另一种用法是菜单。 MenuCommands 示例演示了一个允许在屏幕上对 BoxView
稍作移动的菜单。