ListView
.NET Multi-platform App UI (.NET MAUI) ListView 显示可选择数据项的可滚动垂直列表。 ListView 管理列表的外观时,列表中每个项的外观由使用 Cell 显示项的 DataTemplate 定义。 .NET MAUI 包括可显示文本和图像组合的单元格类型,还可以定义用于显示所需内容的自定义单元格。 ListView 还包括对显示页眉和页脚、分组数据、下拉刷新和上下文菜单项的支持。
ListView 类派生自 ItemsView<Cell>
类,从中继承以下属性:
ItemsSource
,类型为IEnumerable
,用于指定要显示的项集合,其默认值为null
。ItemTemplate
,类型为 DataTemplate,用于指定模板,以应用到要显示的项目集中的各个项。
ListView 定义以下属性:
Footer
,类型为object
,用于指定将在列表末尾显示的字符串或视图。- DataTemplate 类型的
FooterTemplate
指定用于设置Footer
格式的 DataTemplate。 GroupHeaderTemplate
,类型为 DataTemplate,定义用于每个组标头外观的 DataTemplate。 此属性与GroupDisplayBinding
属性互斥。 因此,设置此属性将把GroupDisplayBinding
设置为null
。HasUnevenRows
,类型为bool
,指示列表中的项是否可以具有不同高度的行。 此属性的默认值为false
。Header
,类型为object
,指定将在列表开头显示的字符串或视图。- DataTemplate 类型的
HeaderTemplate
指定用于设置Header
格式的 DataTemplate。 HorizontalScrollBarVisibility
,类型为ScrollBarVisibility
,指示水平滚动条何时可见。IsGroupingEnabled
,类型为bool
,指示是否应在组中显示基础数据。 此属性的默认值为false
。IsPullToRefreshEnabled
,类型为bool
,指示用户是否可以向下轻扫以让 ListView 刷新其数据。 此属性的默认值为false
。IsRefreshing
,类型为bool
,用于指示当前 ListView 是否正在刷新。 此属性的默认值为false
。RefreshCommand
,类型为 ICommand,表示在触发刷新时将执行的命令。RefreshControlColor
,类型为 Color,定义刷新发生时显示的刷新可视化效果的颜色。RowHeight
,类型为int
,确定当HasUnevenRows
为false
时每行的高度。SelectedItem
,类型为object
,表示 ListView 中的当前所选项。SelectionMode
,类型为ListViewSelectionMode
,指示是否可以在 ListView 中选择项。 此属性的默认值为Single
。SeparatorColor
,类型为 Color,定义分隔列表项的条的颜色。SeparatorVisibility
,类型为SeparatorVisibility
,定义项目之间是否可见分隔符。VerticalScrollBarVisibility
,类型为ScrollBarVisibility
,指示垂直滚动条何时可见。
所有这些属性均由 BindableProperty 对象提供支持;也就是说,它们可以作为数据绑定的目标,并能进行样式设置。
此外,ListView 还定义了以下不受 BindableProperty 对象支持的属性:
GroupDisplayBinding
,类型为BindingBase
,用于显示组标头的绑定。 此属性与GroupHeaderTemplate
属性互斥。 因此,设置此属性将把GroupHeaderTemplate
设置为null
。GroupShortNameBinding
,类型为BindingBase
,用于在分组跳转列表中显示的名称的绑定。CachingStrategy
,类型为ListViewCachingStrategy
,定义 ListView 的单元格重用策略。 这是只读属性。
ListView 定义以下事件:
ItemAppearing
,在项的视觉对象表示形式添加到 ListView 的视觉对象布局时引发。 此事件附带的ItemVisibilityEventArgs
对象定义Item
和Index
属性。ItemDisappearing
,在从 ListView 的视觉对象布局中删除项的视觉对象表示形式时引发。 此事件附带的ItemVisibilityEventArgs
对象定义Item
和Index
属性。ItemSelected
,在选定列表中的新项时引发。 此事件附带的SelectedItemChangedEventArgs
对象定义SelectedItem
和SelectedItemIndex
属性。ItemTapped
,在点击 ListView 中的项时引发。 此事件附带的ItemTappedEventArgs
对象定义Group
、Item
和ItemIndex
属性。Refreshing
,在 ListView 上触发下拉刷新操作时引发。Scrolled
。 此事件附带的ScrolledEventArgs
对象定义ScrollX
和ScrollY
属性。ScrollToRequested
。 此事件附带的ScrollToRequestedEventArgs
对象定义 Element、Mode
、Position
、ScrollX
、ScrollY
和ShouldAnimate
属性。
对 ListView 填充数据
通过将 ListView 的 ItemsSource
属性设置为实现 IEnumerable
的任何集合来对其填充数据。
重要
如果在基础集合中添加、移除或更改项时需要刷新 ListView,则基础集合应是发送属性更改通知的 IEnumerable
集合,例如 ObservableCollection
。
通过使用数据绑定将 ListView 的 ItemsSource
属性绑定到 IEnumerable
集合即可对其填充数据。 在 XAML 中,此过程可通过使用 Binding
标记扩展实现:
<ListView ItemsSource="{Binding Monkeys}" />
等效 C# 代码如下:
ListView listView = new ListView();
listView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
在此示例中, ItemsSource
属性数据绑定到所连接视图模型的 Monkeys
属性。
注意
可以启用已编译的绑定,以提高 .NET MAUI 应用程序中的数据绑定性能。 有关详细信息,请参阅编译的绑定。
有关数据绑定的详细信息,请参阅数据绑定。
定义项外观
可以通过将 ItemTemplate
属性设置为 DataTemplate 来定义 ListView 中每个项的外观:
<ListView ItemsSource="{Binding Monkeys}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid Padding="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Grid.RowSpan="2"
Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="60"
WidthRequest="60" />
<Label Grid.Column="1"
Text="{Binding Name}"
FontAttributes="Bold" />
<Label Grid.Row="1"
Grid.Column="1"
Text="{Binding Location}"
FontAttributes="Italic"
VerticalOptions="End" />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
DataTemplate 中指定的元素定义列表中每个项的外观,且 DataTemplate 的子对象必须为 Cell 对象。 在此示例中,DataTemplate 的内部布局由 Grid 管理。 Grid 包含一个 Image 对象和两个 Label 对象,它们都绑定到 Monkey
类的属性:
public class Monkey
{
public string Name { get; set; }
public string Location { get; set; }
public string Details { get; set; }
public string ImageUrl { get; set; }
}
以下屏幕截图显示对列表中每个项进行模板化的结果:
要详细了解数据模板,请参阅数据模板。
单元
ListView 中每个项的外观由 DataTemplate 定义,且 DataTemplate 必须引用 Cell 类才能显示项。 每个单元格表示 ListView 中的一个数据项。 .NET MAUI 包含以下内置单元格:
- TextCell 用于在单独的行上显示主文本和辅助文本。
- ImageCell 用于在单独的行上显示带有主文本和辅助文本的图像。
- SwitchCell 显示文本和一个可以打开或关闭的开关。
- EntryCell 显示可编辑的标签和文本。
- ViewCell 是一个自定义单元格,其外观由 View 定义。 当想要完全定义 ListView 中每个项的外观时,应使用此单元格类型。
通常,SwitchCell 和 EntryCell 仅在 TableView 中使用,不会在 ListView 中使用。 有关 SwitchCell 和 EntryCell 的详细信息,请参阅 TableView。
文本单元格
TextCell 在单独的行上显示主文本和辅助文本。 TextCell 定义以下属性:
Text
,类型为string
,定义要显示的主文本。TextColor
,类型为 Color,表示主文本的颜色。Detail
,类型为string
,定义要显示的辅助文本。DetailColor
,类型为 Color,表示辅助文本的颜色。Command
,类型为 ICommand,定义点击单元格时执行的命令。CommandParameter
,类型为object
,表示传递给命令的参数。
这些属性由 BindableProperty 对象提供支持,表示它们可以是数据绑定的目标,并可以设置样式。
以下示例显示如何使用 TextCell 来定义 ListView 中项的外观:
<ListView ItemsSource="{Binding Food}">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Name}"
Detail="{Binding Description}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
以下屏幕截图展示了生成的单元格外观:
图像单元格
ImageCell 在单独的行上显示包含主文本和辅助文本的图像。 ImageCell 继承来自 TextCell 的属性,并定义类型为 ImageSource 的 ImageSource 属性,该属性指定要在单元格中显示的图像。 此属性由 BindableProperty 对象提供支持,这意味着它可以作为数据绑定的目标,并能进行样式设置。
以下示例展示了如何使用 ImageCell 定义 ListView 中项的外观:
<ListView ItemsSource="{Binding Food}">
<ListView.ItemTemplate>
<DataTemplate>
<ImageCell ImageSource="{Binding Image}"
Text="{Binding Name}"
Detail="{Binding Description}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
以下屏幕截图展示了生成的单元格外观:
视图单元格
ViewCell 是自定义单元格,其外观由 View 定义。 ViewCell 定义类型为 View 的 View 属性,该属性定义用于表示单元格内容的视图。 此属性由 BindableProperty 对象提供支持,这意味着它可以作为数据绑定的目标,并能进行样式设置。
以下示例显示如何使用 ViewCell 来定义 ListView 中项的外观:
<ListView ItemsSource="{Binding Monkeys}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid Padding="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Grid.RowSpan="2"
Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="60"
WidthRequest="60" />
<Label Grid.Column="1"
Text="{Binding Name}"
FontAttributes="Bold" />
<Label Grid.Row="1"
Grid.Column="1"
Text="{Binding Location}"
FontAttributes="Italic"
VerticalOptions="End" />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
在 ViewCell 中,可以通过任何 .NET MAUI 布局来管理布局。 在此示例中,布局由 Grid 管理。 Grid 包含一个 Image 对象和两个 Label 对象,它们都绑定到 Monkey
类的属性。
以下屏幕截图显示对列表中每个项进行模板化的结果:
在运行时选择项外观
通过将 ItemTemplate
属性设置为 DataTemplateSelector 对象,即可在运行时根据项值选择 ListView 中每个项的外观:
<ContentPage ...
xmlns:templates="clr-namespace:ListViewDemos.Templates">
<ContentPage.Resources>
<DataTemplate x:Key="AmericanMonkeyTemplate">
<ViewCell>
...
</ViewCell>
</DataTemplate>
<DataTemplate x:Key="OtherMonkeyTemplate">
<ViewCell>
...
</ViewCell>
</DataTemplate>
<templates:MonkeyDataTemplateSelector x:Key="MonkeySelector"
AmericanMonkey="{StaticResource AmericanMonkeyTemplate}"
OtherMonkey="{StaticResource OtherMonkeyTemplate}" />
</ContentPage.Resources>
<Grid Margin="20">
<ListView ItemsSource="{Binding Monkeys}"
ItemTemplate="{StaticResource MonkeySelector}" />
</Grid>
</ContentPage>
ItemTemplate
属性设置为 MonkeyDataTemplateSelector
对象。 以下示例显示了 MonkeyDataTemplateSelector
类:
public class MonkeyDataTemplateSelector : DataTemplateSelector
{
public DataTemplate AmericanMonkey { get; set; }
public DataTemplate OtherMonkey { get; set; }
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
return ((Monkey)item).Location.Contains("America") ? AmericanMonkey : OtherMonkey;
}
}
MonkeyDataTemplateSelector
类定义 AmericanMonkey
和 OtherMonkey
DataTemplate 属性,这些属性设置为不同的数据模板。 当猴子名称包含“美国”时,OnSelectTemplate
重写函数会返回 AmericanMonkey
模板,该模板以青色显示猴子名称和位置。 当猴子名称中不包含“美国”时,OnSelectTemplate
重写函数将返回 OtherMonkey
模板,该模板以银色显示猴子名称和位置:
要详细了解数据模板选择器,请参阅创建 DataTemplateSelector。
响应项选择
默认情况下,ListView 选择处于启用状态。 但是,可以通过设置 SelectionMode
属性来更改此行为。 ListViewSelectionMode
枚举定义以下成员:
None
– 表示无法选择项。Single
– 表示可以选择单个项,并突出显示选定项。 这是默认值。
ListView 定义当 SelectedItem
属性发生更改时引发的 ItemSelected
事件,变化的原因包括用户从列表中选择项以及应用设置该属性。 此事件附带的 SelectedItemChangedEventArgs
对象包含 SelectedItem
和 SelectedItemIndex
属性。
当 SelectionMode
属性被设置为 Single
时,可以选择 ListView 中的单个项。 选中某个项时,SelectedItem
属性会被设置为所选项的值。 当此属性更改时,将引发 ItemSelected
事件。
以下示例显示了可以响应单项选择的 ListView:
<ListView ItemsSource="{Binding Monkeys}"
ItemSelected="OnItemSelected">
...
</ListView>
在此示例中,在触发 ItemSelected
事件时会执行 OnItemSelected
事件处理程序,事件处理程序会检索所选项:
void OnItemSelected(object sender, SelectedItemChangedEventArgs args)
{
Monkey item = args.SelectedItem as Monkey;
}
以下屏幕截图展示了 ListView 中的单项选择:
清除选择
可以通过将 SelectedItem
属性或其绑定的对象设置为 null
来清除该属性。
禁用选择
默认情况下,ListView 选择处于启用状态。 但是,可以通过将 SelectionMode
属性设置为 None
来禁用它:
<ListView ...
SelectionMode="None" />
当 SelectionMode
属性设置为 None
时,无法选择 ListView 中的项,SelectedItem
属性始终为 null
,且不会触发 ItemSelected
事件。
缓存数据
ListView 是用于显示数据的强大视图,但它有一些限制。 使用自定义单元格时,滚动性能可能会受到影响,尤其是在单元格包含深度嵌套视图层次结构,或使用某些需要复杂度量的布局时。 幸运的是,可以使用一些技巧来避免出现性能欠佳的情况:
ListView 通常用于显示屏幕上容纳不下的大量数据。 例如,音乐应用的歌曲库可能有数千个条目。 为每个条目创建一个项会浪费宝贵的内存,并导致性能欠佳。 如要不断创建和销毁行,应用就必须不断实例化和清理对象,这也会导致性能欠佳。
为了节省内存,每个平台的本机 ListView 等效项都具有可供重复使用行的内置功能。 只有屏幕上可见的单元格才会被载入内存,而内容将加载到现有单元格中。 这种模式可以防止应用实例化数千个对象,从而节省时间和内存。
.NET MAUI 允许通过 ListViewCachingStrategy
枚举重复使用 ListView 单元格,该枚举定义了以下成员:
RetainElement
,指定 ListView 将为列表中的每个项生成一个单元格。RecycleElement
,指定 ListView 将尝试通过回收列表单元格来最大程度地减少其内存占用和降低执行速度。RecycleElementAndDataTemplate
,类似RecycleElement
,同时还确保 DataTemplate 对象在 ListView 使用 DataTemplateSelector 时按列表中的项类型进行缓存。
保留元素
RetainElement
缓存策略指定 ListView 将为列表中的每个项生成一个单元格,并且是默认 ListView 行为。 它应在以下情况下使用:
- 每个单元格都有大量绑定 (20-30+)。
- 单元格模板经常更改。
- 测试表明,
RecycleElement
缓存策略会降低执行速度。
使用自定义单元格时,必须认识到 RetainElement
缓存策略的后果。 每次创建单元格时都需要运行单元格初始化代码,该代码可能需要每秒运行多次。 在这种情况下,原本在页面上没有问题的布局技术(如使用多个嵌套 Grid 对象),在用户滚动时被实时设置和销毁,就会成为性能瓶颈。
回收元素
RecycleElement
缓存策略指定 ListView 将尝试通过回收列表单元格来最大程度减少其内存占用和降低执行速度。 这种模式并不总能提高性能,应执行测试以确定是否有任何改进。 但是,这是首选方案,并应在以下情况下使用:
- 每个单元格具有少量到中等数量的绑定。
- 每个单元格的
BindingContext
定义所有单元格数据。 - 每个单元格大体相似,单元格模板保持不变。
在虚拟化期间,单元格将更新其绑定上下文,因此,如果应用使用这种模式,必须确保妥善处理绑定上下文更新。 有关单元格的所有数据都必须来自绑定上下文,否则可能会出现一致性错误。 可以通过使用数据绑定显示单元格数据来避免此问题。 或者,单元格数据应在 OnBindingContextChanged
重写中设置,而不是在自定义单元格的构造函数中设置,如以下示例所示:
public class CustomCell : ViewCell
{
Image image = null;
public CustomCell()
{
image = new Image();
View = image;
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
var item = BindingContext as ImageItem;
if (item != null)
{
image.Source = item.ImageUrl;
}
}
}
使用 DataTemplateSelector 回收元素
当 ListView 使用 DataTemplateSelector 选择 DataTemplate 时,RecycleElement
缓存策略不会缓存 DataTemplate 对象。 而是为列表中的每个数据项选择 DataTemplate。
注意
RecycleElement
缓存策略要求,当系统要求 DataTemplateSelector 选择 DataTemplate 时,每个 DataTemplate 必须返回相同的 ViewCell 类型。 例如,给定一个带有可返回 MyDataTemplateA
(其中 MyDataTemplateA
返回一个类型为 MyViewCellA
的 ViewCell)或 MyDataTemplateB
(其中 MyDataTemplateB
返回一个类型为 MyViewCellB
的 ViewCell)的 DataTemplateSelector 的 ListView,返回 MyDataTemplateA
时必须返回 MyViewCellA
,否则将引发异常。
使用 DataTemplates 回收元素
RecycleElementAndDataTemplate
缓存策略建立在 RecycleElement
缓存策略基础上,还额外确保了当 ListView 使用 DataTemplateSelector 选择 DataTemplate 时,DataTemplate 对象将按列表中的项类型进行缓存。 因此,会针对每个项类型选择一次 DataTemplate 对象,而不是针对每个项实例选择一次。
注意
RecycleElementAndDataTemplate
缓存策略要求 DataTemplateSelector 返回的 DataTemplate 对象必须使用采用 Type
的 DataTemplate 构造函数。
设置缓存策略
可以通过设置 CachingStrategy
特性在 XAML 中定义 ListView 缓存策略:
<ListView CachingStrategy="RecycleElement">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
...
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
在 C# 中,缓存策略是通过构造函数重载设置的:
ListView listView = new ListView(ListViewCachingStrategy.RecycleElement);
在子类化的 ListView 中设置缓存策略
在子类化的 ListView 上从 XAML 设置 CachingStrategy
特性不会生成所需的行为,因为 ListView 上没有 CachingStrategy
属性。 此问题的解决方案是,在接受 ListViewCachingStrategy
参数并将其传递给基类的子类化 ListView 上指定构造函数:
public class CustomListView : ListView
{
public CustomListView (ListViewCachingStrategy strategy) : base (strategy)
{
}
...
}
然后,可以使用 x:Arguments
特性从 XAML 指定 ListViewCachingStrategy
枚举值:
<local:CustomListView>
<x:Arguments>
<ListViewCachingStrategy>RecycleElement</ListViewCachingStrategy>
</x:Arguments>
</local:CustomListView>
页眉和页脚
ListView 可以显示随列表中的项一起滚动的页眉和页脚。 页眉和页脚可以是字符串、视图或 DataTemplate 对象。
ListView 定义用于指定页眉和页脚的以下属性:
object
类型的Header
指定将在列表开头显示的字符串、绑定或视图。- DataTemplate 类型的
HeaderTemplate
指定用于设置Header
格式的 DataTemplate。 object
类型的Footer
指定将在列表末尾显示的字符串、绑定或视图。- DataTemplate 类型的
FooterTemplate
指定用于设置Footer
格式的 DataTemplate。
这些属性由 BindableProperty 对象提供支持,这意味着它们可以作为数据绑定的目标。
在页眉和页脚中显示字符串
可以将 Header
和 Footer
属性设置为 string
值,如以下示例所示:
<ListView ItemsSource="{Binding Monkeys}"
Header="Monkeys"
Footer="2022">
...
</ListView>
以下屏幕截图显示生成的页眉:
在页眉和页脚中显示视图
可以将 Footer
和 Header
属性都设置为视图。 这可以是单个视图,也可以是包含多个子视图的视图。 以下示例显示 Header
和 Footer
属性,每个属性都设置为包含 Label 对象的 Grid 对象:
<ListView ItemsSource="{Binding Monkeys}">
<ListView.Header>
<Grid BackgroundColor="LightGray">
<Label Margin="10,0,0,0"
Text="Monkeys"
FontSize="12"
FontAttributes="Bold" />
</Grid>
</ListView.Header>
<ListView.Footer>
<Grid BackgroundColor="LightGray">
<Label Margin="10,0,0,0"
Text="Friends of Monkey"
FontSize="12"
FontAttributes="Bold" />
</Grid>
</ListView.Footer>
...
</ListView>
以下屏幕截图显示生成的页眉:
显示模板化页眉和页脚
可以将 HeaderTemplate
和 FooterTemplate
属性设置为用于设置页眉和页脚格式的 DataTemplate 对象。 在此方案中,Header
和 Footer
属性必须绑定到要应用模板的当前源,如以下示例所示:
<ListView ItemsSource="{Binding Monkeys}"
Header="{Binding .}"
Footer="{Binding .}">
<ListView.HeaderTemplate>
<DataTemplate>
<Grid BackgroundColor="LightGray">
<Label Margin="10,0,0,0"
Text="Monkeys"
FontSize="12"
FontAttributes="Bold" />
</Grid>
</DataTemplate>
</ListView.HeaderTemplate>
<ListView.FooterTemplate>
<DataTemplate>
<Grid BackgroundColor="LightGray">
<Label Margin="10,0,0,0"
Text="Friends of Monkey"
FontSize="12"
FontAttributes="Bold" />
</Grid>
</DataTemplate>
</ListView.FooterTemplate>
...
</ListView>
控制项分隔符
默认情况下,在 iOS 和 Android 上,分隔符显示在 ListView 项之间。 可以通过将类型为 SeparatorVisibility
的 SeparatorVisibility
属性设置为 None
来更改此行为:
<ListView ...
SeparatorVisibility="None" />
此外,启用分隔符后,可以使用属性设置 SeparatorColor
颜色:
<ListView ...
SeparatorColor="Blue" />
调整项的大小
默认情况下,ListView 中所有项的高度都相同,这些高度派生自定义每个项外观的 DataTemplate 的内容。 但是,可以使用 HasUnevenRows
和 RowHeight
属性更改此行为。 默认情况下,HasUnevenRows
属性为 false
。
RowHeight
属性可以设置为 int
,表示 ListView 中每个项的高度,前提是 HasUnevenRows
为 false
。 当 HasUnevenRows
设置为 true
时,ListView 中的每个项可以具有不同的高度。 每个项的高度将派生自项的 DataTemplate 的内容,因此每个项的大小都将根据其内容进行调整。
如果 HasUnevenRows
属性为 true
,则可以在运行时通过更改 DataTemplate 中元素的布局相关属性,以编程方式调整单个 ListView 项的大小。 以下示例在点击 Image 对象时更改该对象的高度:
void OnImageTapped(object sender, EventArgs args)
{
Image image = sender as Image;
ViewCell viewCell = image.Parent.Parent as ViewCell;
if (image.HeightRequest < 250)
{
image.HeightRequest = image.Height + 100;
viewCell.ForceUpdateSize();
}
}
在此示例中,在点击 Image 对象时将执行 OnImageTapped
事件处理程序。 事件处理程序会更新 Image 的高度,而 Cell.ForceUpdateSize
方法会更新单元格的大小,即使该单元格当前不可见。
警告
过度使用动态项大小调整可能会导致 ListView 性能下降。
从右到左布局
ListView 可按从右到左的流方向布局其内容,方法是将其 FlowDirection
属性设置为 RightToLeft
。 但是,最好在页面或根布局上设置 FlowDirection
属性,以便页面或根布局中的所有元素都响应流方向:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ListViewDemos.RightToLeftListPage"
Title="Right to left list"
FlowDirection="RightToLeft">
<Grid Margin="20">
<ListView ItemsSource="{Binding Monkeys}">
...
</ListView>
</Grid>
</ContentPage>
包含父元素的元素其默认 FlowDirection
为 MatchParent
。 因此,ListView 从 Grid 继承 FlowDirection
属性值,而后者又从 ContentPage 继承 FlowDirection
属性值。
显示分组数据
在不断滚动的列表中显示时,大型数据集通常会变得笨拙。 在此方案中,将数据组织成组可以简化数据导航,从而改善用户体验。
必须先对数据进行分组,然后才能显示数据。 这可以通过创建组列表来达成此目的,其中每个组都是项的列表。 组列表应为 IEnumerable<T>
集合,其中 T
定义两条数据:
- 组名称。
- 定义组内项的
IEnumerable
集合。
因此,分组数据的过程为:
- 创建对单个项建模的类型。
- 创建对单个项组建模的类型。
- 创建
IEnumerable<T>
集合,其中T
为对单个项组建模的类型。 此集合是组的集合,用于存储分组数据。 - 将数据添加到
IEnumerable<T>
集合。
示例
对数据分组时,第一步是创建对单个项进行建模的类型。 以下示例显示了 Animal
类:
public class Animal
{
public string Name { get; set; }
public string Location { get; set; }
public string Details { get; set; }
public string ImageUrl { get; set; }
}
Animal
类对单个项建模。 然后,可以创建对项组建模的类型。 以下示例显示了 AnimalGroup
类:
public class AnimalGroup : List<Animal>
{
public string Name { get; private set; }
public AnimalGroup(string name, List<Animal> animals) : base(animals)
{
Name = name;
}
}
AnimalGroup
类继承自 List<T>
类,并添加表示组名的 Name
属性。
然后,可以创建 IEnumerable<T>
组集合:
public List<AnimalGroup> Animals { get; private set; } = new List<AnimalGroup>();
此代码定义名为 Animals
的集合,其中集合中的每个项都是一个 AnimalGroup
对象。 每个 AnimalGroup
对象都包含名称和定义组中 Animal
对象的 List<Animal>
集合。
然后,可将分组数据添加到 Animals
集合中:
Animals.Add(new AnimalGroup("Bears", new List<Animal>
{
new Animal
{
Name = "American Black Bear",
Location = "North America",
Details = "Details about the bear go here.",
ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/0/08/01_Schwarzbär.jpg"
},
new Animal
{
Name = "Asian Black Bear",
Location = "Asia",
Details = "Details about the bear go here.",
ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b7/Ursus_thibetanus_3_%28Wroclaw_zoo%29.JPG/180px-Ursus_thibetanus_3_%28Wroclaw_zoo%29.JPG"
},
// ...
}));
Animals.Add(new AnimalGroup("Monkeys", new List<Animal>
{
new Animal
{
Name = "Baboon",
Location = "Africa & Asia",
Details = "Details about the monkey go here.",
ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/f/fc/Papio_anubis_%28Serengeti%2C_2009%29.jpg/200px-Papio_anubis_%28Serengeti%2C_2009%29.jpg"
},
new Animal
{
Name = "Capuchin Monkey",
Location = "Central & South America",
Details = "Details about the monkey go here.",
ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/4/40/Capuchin_Costa_Rica.jpg/200px-Capuchin_Costa_Rica.jpg"
},
new Animal
{
Name = "Blue Monkey",
Location = "Central and East Africa",
Details = "Details about the monkey go here.",
ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/8/83/BlueMonkey.jpg/220px-BlueMonkey.jpg"
},
// ...
}));
此代码在 Animals
集合中创建两个组。 第一个 AnimalGroup
名为 Bears
,包含熊详细信息的 List<Animal>
集合。 第二个 AnimalGroup
名为 Monkeys
,包含猴子详细信息的 List<Animal>
集合。
如果数据已正确分组,ListView 将通过将 IsGroupingEnabled
属性设置为 true
来显示分组数据:
<ListView ItemsSource="{Binding Animals}"
IsGroupingEnabled="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid Padding="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Grid.RowSpan="2"
Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="60"
WidthRequest="60" />
<Label Grid.Column="1"
Text="{Binding Name}"
FontAttributes="Bold" />
<Label Grid.Row="1"
Grid.Column="1"
Text="{Binding Location}"
FontAttributes="Italic"
VerticalOptions="End" />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
等效 C# 代码如下:
ListView listView = new ListView
{
IsGroupingEnabled = true
};
listView.SetBinding(ItemsView.ItemsSourceProperty, "Animals");
// ...
通过将 ItemTemplate
属性设置为 DataTemplate 来定义 ListView 中每个项目的外观。 有关详细信息,请参阅定义项外观。
以下屏幕截图显示了 ListView 展示分组数据:
注意
默认情况下,ListView 将在组标头中显示组名称。 可以通过自定义组标头来更改此行为。
自定义组标头
通过将 ListView.GroupHeaderTemplate
属性设置为 DataTemplate,即可自定义每个组标头的外观:
<ListView ItemsSource="{Binding Animals}"
IsGroupingEnabled="True">
<ListView.GroupHeaderTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding Name}"
BackgroundColor="LightGray"
FontSize="18"
FontAttributes="Bold" />
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
...
</ListView>
在此示例中,每个组标头都设置为显示组名的 Label,并且设置了其他外观属性。 以下屏幕截图显示了自定义组标头:
重要
GroupHeaderTemplate
属性和 GroupDisplayBinding
属性互斥。 因此,不应设置这两个属性。
没有模板的组
ListView 可以正确显示分组数据,而无需将 ItemTemplate
属性设置为 DataTemplate:
<ListView ItemsSource="{Binding Animals}"
IsGroupingEnabled="true" />
在此方案中,可以通过替代单个项建模类型和单个项组建模类型中的 ToString
方法来显示有意义的数据。
控制滚动
ListView 定义两种 ScrollTo
方法,用于将项滚动到视图中。 其中一种重载会将指定项滚动到视图中,而另一种重载会将指定组中的指定项滚动到视图中。 这两种重载都有附加参数,这些参数允许指定滚动完成后项的确切位置,以及是否对滚动进行动画处理。
ListView 定义调用 ScrollTo
方法之一时触发的 ScrollToRequested
事件。 ScrollToRequestedEventArgs
事件附带的 ScrollToRequested
对象包含许多属性,包括 ShouldAnimate
、Element、Mode
和 Position
。 其中一些属性是根据 ScrollTo
方法调用中指定的参数设置的。
此外,ListView 还定义一个 Scrolled
事件,该事件被触发以指示发生了滚动。 Scrolled
事件附带的 ScrolledEventArgs
对象包含 ScrollX
和 ScrollY
属性。
检测滚动
ListView 定义了一个 Scrolled
事件,该事件被触发以指示发生了滚动。 ItemsViewScrolledEventArgs
类表示 Scrolled
事件随附的对象,它定义下列属性:
ScrollX
,类型为double
,表示滚动的 X 位置ScrollY
,类型为double
,表示滚动的 Y 位置。
以下 XAML 示例显示为 Scrolled
事件设置事件处理程序的 ListView:
<ListView Scrolled="OnListViewScrolled">
...
</ListView>
等效 C# 代码如下:
ListView listView = new ListView();
listView.Scrolled += OnListViewScrolled;
在此代码示例中,Scrolled
事件触发时,将执行 OnListViewScrolled
事件处理程序:
void OnListViewScrolled(object sender, ScrolledEventArgs e)
{
// Custom logic
}
重要
会针对用户启动的滚动和编程滚动触发 Scrolled
事件。
将项滚动到视图中
ScrollTo
方法将指定项滚动到视图中。 给定一个名为 listView
的 ListView 对象,以下示例显示如何将 Proboscis Monkey 项滚动到视图中:
MonkeysViewModel viewModel = BindingContext as MonkeysViewModel;
Monkey monkey = viewModel.Monkeys.FirstOrDefault(m => m.Name == "Proboscis Monkey");
listView.ScrollTo(monkey, ScrollToPosition.MakeVisible, true);
或者,可以通过指定项和组来将分组数据中的项滚动到视图中。 以下示例演示如何将“猴子”组中的“长鼻猴”项滚动到视图中:
GroupedAnimalsViewModel viewModel = BindingContext as GroupedAnimalsViewModel;
AnimalGroup group = viewModel.Animals.FirstOrDefault(a => a.Name == "Monkeys");
Animal monkey = group.FirstOrDefault(m => m.Name == "Proboscis Monkey");
listView.ScrollTo(monkey, group, ScrollToPosition.MakeVisible, true);
注意
在调用 ScrollTo
方法时触发 ScrollToRequested
事件。
禁用滚动动画
将项滚动到视图中时,将显示滚动动画。 但是,可以通过将 ScrollTo
方法的 animated
参数设置为 false
来禁用此动画:
listView.ScrollTo(monkey, position: ScrollToPosition.MakeVisible, animate: false);
控制滚动位置
将项滚动到视图中时,可以使用 ScrollTo
方法的 position
参数指定项在滚动完成后的确切位置。 此参数接受 ScrollToPosition
枚举成员。
MakeVisible
ScrollToPosition.MakeVisible
成员指示应滚动该项,直到它在视图中可见:
listView.ScrollTo(monkey, position: ScrollToPosition.MakeVisible, animate: true);
开始
ScrollToPosition.Start
成员指示应将项滚动到视图的开头:
listView.ScrollTo(monkey, position: ScrollToPosition.Start, animate: true);
中心
ScrollToPosition.Center
成员指示应将项滚动到视图的中心:
listView.ScrollTo(monkey, position: ScrollToPosition.Center, animate: true);
End
ScrollToPosition.End
应将项滚动到视图的末尾:
listView.ScrollTo(monkey, position: ScrollToPosition.End, animate: true);
滚动条可见性
ListView 定义 HorizontalScrollBarVisibility
和 VerticalScrollBarVisibility
属性,它们由可绑定的属性提供支持。 这些属性会获取或设置一个 ScrollBarVisibility
枚举值,指示水平滚动条或垂直滚动条何时可见。 ScrollBarVisibility
枚举定义以下成员:
Default
指示平台的默认滚动条行为,并且是HorizontalScrollBarVisibility
和VerticalScrollBarVisibility
属性的默认值。Always
指示滚动条将可见,即使内容大小适合视图。Never
指示滚动条将不可见,即使内容大小不适合视图。
添加上下文菜单
ListView 支持上下文菜单项,这些项被定义为 MenuItem 对象,并添加到每个项的 DataTemplate 中的 ViewCell.ContextActions
集合:
<ListView x:Name="listView"
ItemsSource="{Binding Monkeys}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.ContextActions>
<MenuItem Text="Favorite"
Command="{Binding Source={x:Reference listView}, Path=BindingContext.FavoriteCommand}"
CommandParameter="{Binding}" />
<MenuItem Text="Delete"
Command="{Binding Source={x:Reference listView}, Path=BindingContext.DeleteCommand}"
CommandParameter="{Binding}" />
</ViewCell.ContextActions>
...
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
右键单击 ListView 中的项时,将显示 MenuItem 对象:
有关菜单项的详细信息,请参阅显示菜单项。
下拉以刷新
ListView 支持下拉以刷新功能,通过向下拉动项目列表,可刷新显示数据。
若要启用下拉刷新,请将 IsPullToRefreshEnabled
属性设置为 true
。 触发刷新时, ListView 会引发 Refreshing
事件,并将 IsRefreshing
属性设置为 true
。 然后,刷新 ListView 内容所需的代码应由 Refreshing
事件的处理程序来执行,或由 RefreshCommand
执行的 ICommand 实现来执行。 刷新 ListView 后,应将 IsRefreshing
属性设置为 false
,或在 ListView 上调用 EndRefresh
方法,以指示刷新已完成。
以下示例演示了一个使用下拉刷新的 ListView:
<ListView ItemsSource="{Binding Animals}"
IsPullToRefreshEnabled="true"
RefreshCommand="{Binding RefreshCommand}"
IsRefreshing="{Binding IsRefreshing}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
...
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
在此示例中,当用户启动刷新时,系统将执行 RefreshCommand
属性定义的 ICommand,从而刷新显示项。 刷新时会显示刷新可视化效果,该效果由动画进度圆组成。 IsRefreshing
属性的值指示刷新操作的当前状态。 触发刷新时,此属性将自动转换为 true
。 刷新完成后,应将属性重置为 false
。