ListView

Browse sample. 浏览示例

.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,指定将在列表末尾显示的字符串或视图。
  • FooterTemplate,类型为 DataTemplate,指定用于设置 Footer 格式的 DataTemplate
  • GroupHeaderTemplate,类型为 DataTemplate,定义用于定义每个组标头外观的 DataTemplate。 此属性与 GroupDisplayBinding 属性互斥。 因此,设置此属性会将 GroupDisplayBinding 设置为 null
  • HasUnevenRows,类型为 bool,指示列表中的项是否可以具有高度不同的行。 此属性的默认值为 false
  • Header,类型为 object,指定将在列表开头显示的字符串或视图。
  • HeaderTemplate,类型为 DataTemplate,指定用于设置 Header 格式的 DataTemplate
  • HorizontalScrollBarVisibility,类型为 ScrollBarVisibility,指示水平滚动条何时可见。
  • IsGroupedEnabled,类型为 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 对象可定义 ItemIndex 属性。
  • ItemDisappearing,在从 ListView 的视觉对象布局中删除项的视觉对象表示形式时引发。 此事件随附的 ItemVisibilityEventArgs 对象可定义 ItemIndex 属性。
  • ItemSelected,在选定了列表中的新项时引发。 此事件随附的 SelectedItemChangedEventArgs 对象可定义 SelectedItem 和 SelectedItemIndex 属性。
  • ItemTapped,在点击 ListView 中的项时引发。 此事件随附的 ItemTappedEventArgs 对象可定义 GroupItem 和 ItemIndex 属性。
  • Refreshing,在 ListView 上触发下拉以刷新操作时引发。
  • Scrolled, . 此事件随附的 ScrolledEventArgs 对象可定义 ScrollX 和 ScrollY 属性:
  • ScrollToRequested . 此事件随附的 ScrollToRequestedEventArgs 对象可定义 ElementModePositionScrollXScrollYShouldAnimate 属性。

使用数据填充 ListView

可通过将其 ItemsSource 属性设置为实现 IEnumerable 的任何集合来为 ListView 填充数据。

重要

如果在基础集合中添加、删除或更改项时需要使用 ListView 来进行刷新,则基础集合应是发送属性更改通知的 IEnumerable 集合,例如 ObservableCollection

可通过使用数据绑定将其 ItemsSource 属性绑定到 IEnumerable 集合来为 ListView 填充数据。 在 XAML 中,可通过 Binding 标记扩展达成此目的:

<ListView ItemsSource="{Binding Monkeys}" />

等效 C# 代码如下:

ListView listView = new ListView();
listView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");

在此示例中,ItemsSource 属性数据绑定到连接 viewmodel 的 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; }
}

以下屏幕截图显示对列表中每个项进行模板化的结果:

Screenshot of ListView where each item is templated.

有关数据模板的详细信息,请参阅数据模板

单元

ListView 中每个项的外观由 DataTemplate 定义,且 DataTemplate 必须引用 Cell 类来显示项。 每个单元格表示 ListView 中的数据项。 .NET MAUI 包含以下内置单元格:

  • TextCell,用于在单独的行上显示主要文本和辅助文本。
  • ImageCell,用于在单独的行上显示带有主要文本和辅助文本的图像。
  • SwitchCell,用于显示文本以及可打开或关闭的开关。
  • EntryCell,用于显示可编辑的标签和文本。
  • ViewCell,是一个自定义单元格,其外观由 View 定义。 如果要完全定义 ListView 中每个项的外观,则应使用此单元格类型。

通常,SwitchCellEntryCell 仅用于 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>

以下屏幕截图展示了生成的单元格外观:

Screenshot of ListView where each item is a TextCell.

图像单元格

ImageCell 在单独的行上显示带有主要文本和辅助文本的图像。 ImageCellTextCell 继承属性,并定义 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>

以下屏幕截图展示了生成的单元格外观:

Screenshot of ListView where each item is an ImageCell.

查看单元格

ViewCell 是自定义单元格,其外观由 View 定义。 ViewCell 定义类型为 ViewView 属性,该属性可定义表示单元格内容的视图。 此属性由 BindableProperty 对象提供支持,这意味着可以将它用作数据绑定的目标,并能对它进行样式设置。

注意

由于 View 属性是 ViewCell 类的内容属性,因此不需要通过 XAML 显式设置。

以下示例展示了如何使用 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 类的属性。

以下屏幕截图显示对列表中每个项进行模板化的结果:

Screenshot of ListView where each item is templated with a ViewCell.

在运行时选择项外观

通过将 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>

    <StackLayout Margin="20">
        <ListView ItemsSource="{Binding Monkeys}"
                  ItemTemplate="{StaticResource MonkeySelector}" />
    </StackLayout>
</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 类定义 AmericanMonkeyOtherMonkeyDataTemplate 属性,它们被设置为不同的数据模板。 当猴子名称包含“美国”时,OnSelectTemplate 重写函数会返回 AmericanMonkey 模板,该模板以青色显示猴子名称和位置。 当猴子名称不包含“美国”时,OnSelectTemplate 重写函数会返回 OtherMonkey 模板,该模板以银色显示猴子名称和位置:

Screenshot of ListView runtime item template selection.

有关数据模板选择器的详细信息,请参阅创建 DataTemplateSelector

响应项选择

默认情况下,ListView 选择处于启用状态。 但是,可以通过设置 SelectionMode 属性来更改此行为。 ListViewSelectionMode 枚举定义下列成员:

  • None – 指示无法选择项。
  • Single – 指示可以选择单个项,选定项会突出显示。 这是默认值。

ListView 定义当 SelectedItem 属性发生更改时引发的 ItemSelected 事件,变化的原因包括用户从列表中选择项以及应用设置该属性。 此事件附带的 SelectedItemChangedEventArgs 对象包含 SelectedItemSelectedItemIndex 属性。

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 中选择的单个项:

Screenshot of a ListView with a selection.

清除选择

可以通过将 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 缓存策略的后果。 每次创建单元格时都需要运行单元格初始化代码,该代码可能会每秒运行多次。 在这种情况下,页面上这种原本正常的布局技术(如使用多个嵌套 StackLayout 对象)若在用户滚动时实时设置和销毁,就会成为性能瓶颈。

回收元素

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 返回一个类型为 MyViewCellAViewCell)或 MyDataTemplateB(其中 MyDataTemplateB 返回一个类型为 MyViewCellBViewCell)的 DataTemplateSelectorListView,返回 MyDataTemplateA 时必须返回 MyViewCellA,否则将引发异常。

使用 DataTemplates 回收元素

RecycleElementAndDataTemplate 缓存策略建立在 RecycleElement 缓存策略基础上,还额外确保了当 ListView 使用 DataTemplateSelector 选择 DataTemplate 时,DataTemplate 对象将按列表中的项类型进行缓存。 因此,会针对每个项类型选择一次 DataTemplate 对象,而不是针对每个项实例选择一次。

注意

RecycleElementAndDataTemplate 缓存策略要求由 DataTemplateSelector 返回的 DataTemplate 对象必须使用采用 TypeDataTemplate 构造函数。

设置缓存策略

在 XAML 中,可以通过设置 CachingStrategy 特性来定义 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 定义用于指定页眉和页脚的以下属性:

  • Header,类型为 object,指定将在列表开头显示的字符串、绑定或视图。
  • HeaderTemplate,类型为 DataTemplate,指定用于设置 Header 格式的 DataTemplate
  • Footer,类型为 object,指定将在列表末尾显示的字符串、绑定或视图。
  • FooterTemplate,类型为 DataTemplate,指定用于设置 Footer 格式的 DataTemplate

所有这些属性都由 BindableProperty 对象提供支持,这意味着这些属性可以作为数据绑定的目标。

但是,可以将 HeaderFooter 属性设置为 string 值,如以下示例中所示:

<ListView ItemsSource="{Binding Monkeys}"
          Header="Monkeys"
          Footer="2022">
    ...
</ListView>

以下屏幕截图显示生成的页眉:

Screenshot of a ListView string header.

可以分别将 HeaderFooter 属性设置为视图。 这可以是单个视图,也可以是包含多个子视图的视图。 以下示例展示了 HeaderFooter 属性均被设置为 StackLayout 对象,该对象包含一个 Label 对象:

<ListView ItemsSource="{Binding Monkeys}">
    <ListView.Header>
        <StackLayout BackgroundColor="LightGray">
            <Label Margin="10,0,0,0"
                   Text="Monkeys"
                   FontSize="12"
                   FontAttributes="Bold" />
        </StackLayout>
    </ListView.Header>
    <ListView.Footer>
        <StackLayout BackgroundColor="LightGray">
            <Label Margin="10,0,0,0"
                   Text="Friends of Monkey"
                   FontSize="12"
                   FontAttributes="Bold" />
        </StackLayout>
    </ListView.Footer>
    ...
</ListView>

以下屏幕截图显示生成的页眉:

Screenshot of CollectionView header and footer using views.

可以将 HeaderTemplateFooterTemplate 属性设置为 DataTemplate 对象,该对象用于设置页眉和页脚格式。 在此方案中,对于要应用的模板,HeaderFooter 属性必须绑定到当前源,如以下示例所示:

<ListView ItemsSource="{Binding Monkeys}"
          Header="{Binding .}"
          Footer="{Binding .}">
    <ListView.HeaderTemplate>
        <DataTemplate>
            <StackLayout BackgroundColor="LightGray">
                <Label Margin="10,0,0,0"
                       Text="Monkeys"
                       FontSize="12"
                       FontAttributes="Bold" />
            </StackLayout>
        </DataTemplate>
    </ListView.HeaderTemplate>
    <ListView.FooterTemplate>
        <DataTemplate>
            <StackLayout BackgroundColor="LightGray">
                <Label Margin="10,0,0,0"
                       Text="Friends of Monkey"
                       FontSize="12"
                       FontAttributes="Bold" />
            </StackLayout>
        </DataTemplate>
    </ListView.FooterTemplate>
    ...
</ListView>

控制项分隔符

默认情况下,iOS 和 Android 上的 ListView 项之间会显示分隔符。 可以通过将类型为 SeparatorVisibilitySeparatorVisibility 属性设置为 None 来更改此行为:

<ListView ...
          SeparatorVisibility="None" />

此外,启用分隔符后,还可以使用 SeparatorColor 属性设置其颜色:

<ListView ...
          SeparatorColor="Blue" />

调整项的大小

默认情况下,ListView 中所有项的高度都相同,这些高度派生自定义每个项外观的 DataTemplate 的内容。 但是,可以使用 HasUnevenRowsRowHeight 属性更改此行为。 默认情况下,HasUnevenRows 属性为 false

如果 HasUnevenRowsfalse,则 RowHeight 属性可以设置为表示 ListView 中每个项高度的 int。 将 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">
    <StackLayout Margin="20">
        <ListView ItemsSource="{Binding Monkeys}">
            ...
        </ListView>
    </StackLayout>
</ContentPage>

具有父元素的元素默认 FlowDirectionMatchParent。 因此,ListView 会从 StackLayout 继承 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> 集合。

如果已通过将 IsGroupingEnabled 属性设置为 true 对数据进行了正确分组,则 ListView 会显示分组数据:

<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

Screenshot of grouped data in a 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,并且设置了其他外观属性。 以下屏幕截图显示了自定义组标头:

Screenshot of a customized group header in a ListView.

重要

GroupHeaderTemplate 属性与 GroupDisplayBinding 属性互斥。 因此,不应设置这两个属性。

没有模板的组

ListView 可在不将 ItemTemplate 属性设置为 DataTemplate 的情况下正确显示分组数据:

<ListView ItemsSource="{Binding Animals}"
          IsGroupingEnabled="true" />

在此方案中,可以通过替代单个项建模类型和单个项组建模类型中的 ToString 方法来显示有意义的数据。

控制滚动

ListView 定义两种 ScrollTo 方法,用于将项滚动到视图中。 其中一种重载会将指定项滚动到视图中,而另一种重载会将指定组中的指定项滚动到视图中。 这两种重载都有附加参数,这些参数允许指定滚动完成后项的确切位置,以及是否对滚动进行动画处理。

ListView 定义了调用 ScrollTo 方法之一时触发的 ScrollToRequested 事件。 ScrollToRequestedEventArgs 事件随附的 ScrollToRequested 对象包含许多属性,包括 ShouldAnimateElementModePosition。 其中一些属性是根据 ScrollTo 方法调用中指定的参数设置的。

此外, ListView 还定义触发以指示发生了滚动的 Scrolled 事件。 Scrolled 事件随附的 ScrolledEventArgs 对象包含 ScrollXScrollY 属性。

检测滚动

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 方法将指定项滚动到视图中。 给定名为 listViewListView 对象,以下示例演示如何将“长鼻猴”项滚动到视图中:

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 定义 HorizontalScrollBarVisibilityVerticalScrollBarVisibility 属性,这些属性由可绑定属性提供支持。 这些属性会获取或设置一个 ScrollBarVisibility 枚举值,指示水平滚动条或垂直滚动条何时可见。 ScrollBarVisibility 枚举定义下列成员:

  • Default 指示平台的默认滚动条行为,并且是 HorizontalScrollBarVisibilityVerticalScrollBarVisibility 属性的默认值。
  • 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 对象:

Screenshot of CollectionView context menu items.

有关菜单项的详细信息,请参阅显示菜单项

下拉刷新

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