ListView

浏览示例。浏览该示例

.NET Multi-platform App UI (.NET MAUI) ListView 显示一个可滚动的垂直列表,其中包含可选择的数据项。 在 ListView 管理列表的外观时,列表中每个项的外观由使用 DataTemplate 显示项的 Cell 定义。 .NET MAUI 包括可显示文本和图像组合的单元格类型,还可以定义用于显示所需内容的自定义单元格。 ListView 还提供对显示页眉和页脚、分组数据、下拉刷新功能以及上下文菜单项的支持。

重要

ListView 已被弃用,和 EntryCellImageCellSwitchCellTextCell、以及 ViewCell 一样。 相反,应使用 CollectionView

Cell 尚未弃用,因为它目前用于源代码生成。 但是,应考虑将其弃用。

ListView 类派生自 ItemsView<Cell> 类,从中继承以下属性:

  • ItemsSource,类型为 IEnumerable,用于指定要显示的项集合,其默认值为 null
  • ItemTemplate,类型为 DataTemplate,用于指定模板,以应用于显示项目集合中的每个项目。

ListView 定义以下属性:

  • Footer,类型为 object,用于指定将在列表末尾显示的字符串或视图。
  • 类型为 DataTemplateFooterTemplate 指定用于格式化 FooterDataTemplate
  • GroupHeaderTemplate,类型为DataTemplate,定义了用于定义每个组标头外观的DataTemplate。 此属性与 GroupDisplayBinding 属性互斥。 因此,设置此属性将把 GroupDisplayBinding 设置为 null
  • HasUnevenRows,一种 bool 类型,指示列表中的项是否可以有不同高度的行。 此属性的默认值为 false
  • Header,类型为 object,指定将在列表开头显示的字符串或视图。
  • HeaderTemplate 类型的 DataTemplate 指定用于格式化 HeaderDataTemplate
  • HorizontalScrollBarVisibility,类型为 ScrollBarVisibility,指示水平滚动条何时可见。
  • IsGroupingEnabled,类型为 bool,指示基础数据是否应在组中显示。 此属性的默认值为 false
  • IsPullToRefreshEnabled,类型为 bool,指示用户是否可以向下轻扫以让 ListView 刷新其数据。 此属性的默认值为 false.
  • IsRefreshing,类型为bool,用于指示ListView是否正在刷新。 此属性的默认值为false
  • RefreshCommand,类型为 ICommand,表示在触发刷新时将执行的命令。
  • RefreshControlColor,类型为 Color,定义刷新发生时显示的刷新可视化效果的颜色。
  • RowHeight,类型为 int,确定当 HasUnevenRowsfalse 时每行的高度。
  • 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,当一个项的视觉表现从 <列表视图> 的视觉布局中移除时引发。 此事件附带的 ItemVisibilityEventArgs 对象定义 ItemIndex 属性。
  • ItemSelected,在选择列表中新项目时触发。 此事件附带的 SelectedItemChangedEventArgs 对象定义 SelectedItemSelectedItemIndex 属性。
  • `ItemTapped,当用户点击ListView中的某个项时触发。` 此事件附带的 ItemTappedEventArgs 对象定义 GroupItemItemIndex 属性。
  • Refreshing,ListView 上触发下拉刷新操作时引发。
  • Scrolled,滚动。 此事件附带的 ScrolledEventArgs 对象定义 ScrollXScrollY 属性。
  • ScrollToRequested。 此事件附带的 ScrollToRequestedEventArgs 对象定义 ElementModePositionScrollXScrollYShouldAnimate 属性。

对 ListView 填充数据

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

重要

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

通过使用数据绑定可以将ListViewItemsSource属性绑定到IEnumerable集合,从而为其填充数据。 在 XAML 中,此过程可通过使用 Binding 标记扩展实现:

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

等效 C# 代码如下:

ListView listView = new ListView();
listView.SetBinding(ItemsView.ItemsSourceProperty,  static (MonkeysViewModel vm) => vm.Monkeys);

在此示例中,ItemsSource 属性数据绑定到所连接视图模型的 Monkeys 属性。

注意

可以启用已编译的绑定,以提高 .NET MAUI 应用程序中的数据绑定性能。 有关详细信息,请参阅<编译绑定>.

有关数据绑定的详细信息,请参阅 [数据绑定](../../fundamentals/data-binding/)。

定义项外观

可以通过将 ItemTemplate 属性设置为 DataTemplate 来定义 ListView 中每个项的外观:

<ListView ItemsSource="{Binding Monkeys}">
    <ListView.ItemTemplate>
        <DataTemplate x:DataType="models:Monkey">
            <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 的屏幕截图,其中每个项都已模板化。

要了解有关数据模板的更多信息,请参阅数据模板

细胞

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

  • TextCell 用于在单独的行上显示主文本和辅助文本。
  • ImageCell 用于将图像与主文本和辅助文本分别显示在不同的行上。
  • SwitchCell 显示文本和一个可以打开或关闭的开关。
  • EntryCell 显示标签和可编辑的文本。
  • ViewCell, 是一个自定义的单元格,其外观由 View 定义。 当您想要完全定义 ListView 中每个项的外观时,应使用此单元格类型。

通常,SwitchCellEntryCell 仅在 TableView 中使用,不会在 ListView 中使用。 有关 SwitchCellEntryCell 的详细信息,请参阅 TableView

文本单元格

TextCell 将主文本和次文本显示在不同的行上。 TextCell 定义了以下属性:

  • Text,类型为 string,定义要显示的主文本。
  • TextColor,类型为Color,表示主文本的颜色。
  • Detail,类型为 string,用于定义要显示的辅助文本。
  • DetailColor,类型为Color,表示辅助文本的颜色。
  • Command,其类型为ICommand,定义了点击单元格时执行的命令。
  • ,类型为 ,表示传递给命令的参数。

这些属性由 BindableProperty 对象提供支持,这意味着它们可以用于数据绑定,并可以设置样式。

以下示例显示如何使用 TextCell 来定义 ListView 中项的外观:

<ListView ItemsSource="{Binding Food}">
    <ListView.ItemTemplate>
        <DataTemplate x:DataType="local:FoodItem">
            <TextCell Text="{Binding Name}"
                      Detail="{Binding Description}" />
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

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

ListView 的截图,其中每个项目都是一个 TextCell。

图像单元格

ImageCell 显示一张图片,并在单独的行上配有主文本和辅助文本。 ImageCell 继承了 TextCell 的属性,并定义了一个类型为 ImageSource 的属性,该属性指定要在单元格中显示的图像。 此属性由 BindableProperty 对象提供支持,这意味着它可以作为数据绑定的目标,并能进行样式设置。

以下示例展示了如何使用 ImageCell 来定义 ListView 中各项的外观:

<ListView ItemsSource="{Binding Food}">
    <ListView.ItemTemplate>
        <DataTemplate x:DataType="local:FoodItem">
            <ImageCell ImageSource="{Binding Image}"
                       Text="{Binding Name}"
                       Detail="{Binding Description}" />
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

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

ListView 的截图,其中每个项目都是 ImageCell。

查看单元格

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

注意

View 属性是 ViewCell 类的内容属性,因此不需要在 XAML 中显式设置。

以下示例显示如何使用 ViewCell 来定义 ListView 中项的外观:

<ListView ItemsSource="{Binding Monkeys}">
    <ListView.ItemTemplate>
        <DataTemplate x:DataType="models:Monkey">
            <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 管理。 包含一个 对象和两个 对象,它们都绑定到 类的属性。

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

Screenshot of ListView where each item is templated with a ViewCell.ListView 的屏幕截图,其中每个项目都使用 ViewCell 作为模板。

在运行时选择项目外观

可以通过将 ItemTemplate 属性设置为 DataTemplateSelector 对象,在运行时根据项值选择 ListView 中每个项的外观。

<ContentPage ...
             xmlns:templates="clr-namespace:ListViewDemos.Templates"
             xmlns:viewmodels="clr-namespace:ListViewDemos.ViewModels"
             x:DataType="viewmodels:MonkeysViewModel">
    <ContentPage.Resources>
        <DataTemplate x:Key="AmericanMonkeyTemplate"
                      x:DataType="models:Monkey">
            <ViewCell>
                ...
            </ViewCell>
        </DataTemplate>

        <DataTemplate x:Key="OtherMonkeyTemplate"
                      x:DataType="models:Monkey">
            <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 类定义 AmericanMonkeyOtherMonkeyDataTemplate 属性,这些属性设置为不同的数据模板。 当猴子名称包含“America”时,OnSelectTemplate 重写返回 AmericanMonkey 模板,该模板以青色显示猴子的名称和位置。 当猴子名称中不包含“美国”时,OnSelectTemplate 重写将返回 OtherMonkey 模板,该模板以银色显示猴子名称和位置:

ListView 运行时项模板选择的屏幕截图。

要详细了解数据模板选择器,请参阅创建 DataTemplateSelector

响应项选择

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

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

ListView 定义一个事件,当 SelectedItem 属性更改时引发,该更改可能是由于用户从列表中选择项目或应用设置属性引起的。 该事件附带的 SelectedItemChangedEventArgs 对象具有 SelectedItemSelectedItemIndex 属性。

SelectionMode 属性设置为 Single 时,可以选择 ListView 中的一项。 选中某个项时,SelectedItem 属性会被设置为所选项的值。 当此属性更改时,将触发 ItemSelected 事件。

以下示例显示了可以响应单项选择的 ListView

<ListView ItemsSource="{Binding Monkeys}"
          ItemSelected="OnItemSelected">
    ...
</ListView>

在此示例中,OnItemSelected 事件处理程序会在 ItemSelected 事件触发时执行,并检索所选项:

void OnItemSelected(object sender, SelectedItemChangedEventArgs args)
{
    Monkey item = args.SelectedItem as Monkey;
}

以下屏幕截图展示了 ListView 中的单项选择:

Screenshot of a ListView with a selection.ListView 的屏幕截图,其中包含所选内容。

清除选择

可以通过将 SelectedItem 属性或它绑定到的对象设置为 null 来清除该属性。

禁用选择

ListView 在默认情况下,选择处于启用状态。 但是,可以通过将 SelectionMode 属性设置为 None 来禁用它:

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

SelectionMode 属性设置为 None 时,ListView 中的项无法被选择,SelectedItem 属性将保持为 null,并且 ItemSelected 事件不会被触发。

缓存数据

ListView 是用于显示数据的功能强大的视图,但它有一些限制。 使用自定义单元格时,滚动性能可能会受到影响,尤其是在单元格包含深度嵌套视图层次结构,或使用某些需要复杂度量的布局时。 幸运的是,可以使用一些技巧来避免出现性能欠佳的情况:

一个ListView通常用于显示超过屏幕显示容量的数据。 例如,音乐应用的歌曲库可能有数千个条目。 为每个条目创建一个项会浪费宝贵的内存,并导致性能欠佳。 如要不断创建和销毁行,应用就必须不断实例化和清理对象,这也会导致性能欠佳。

为了节省内存,每个平台的本机等效项都有内置功能来重复使用行。 只有屏幕上可见的单元格才会被载入内存,而内容将加载到现有单元格中。 这种模式可以防止应用实例化数千个对象,从而节省时间和内存。

.NET MAUI 允许 ListView 单元格通过 ListViewCachingStrategy 枚举重复使用,该枚举定义了以下成员:

  • RetainElement,指定ListView将为列表中的每个项生成一个单元格。
  • RecycleElement,指定 ListView 将尝试通过回收列表单元格来最大程度地减少其内存占用并提高执行速度。
  • RecycleElementAndDataTemplate,类似RecycleElement,同时还要确保在ListView使用DataTemplateSelector时,DataTemplate对象是根据列表中项的类型进行缓存的。

保留元素

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 类型。 例如,对于一个带有 DataTemplateSelectorListView(其中 MyDataTemplateA 返回一个类型为 MyViewCellAViewCell)或 MyDataTemplateB(其中 MyDataTemplateB 返回一个类型为 MyViewCellBViewCell)的情况,当返回 MyDataTemplateA 时,必须返回 MyViewCellA,否则将会抛出异常。

使用 DataTemplates 回收元素

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

注意

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

设置缓存策略

可以通过在 XAML 中设置 CachingStrategy 属性来定义 ListView 缓存策略:

<ListView CachingStrategy="RecycleElement">
    <ListView.ItemTemplate>
        <DataTemplate>
            <ViewCell>
              ...
            </ViewCell>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

在 C# 中,缓存策略是通过构造函数重载设置的:

ListView listView = new ListView(ListViewCachingStrategy.RecycleElement);

在子类化的 ListView 中设置缓存策略

在 XAML 中设置子类化的 ListViewCachingStrategy 特性不会产生预期的行为,因为 ListView 上没有 CachingStrategy 属性。 此问题的解决方案是在子类化的ListView上指定一个构造函数,该构造函数接受ListViewCachingStrategy参数,并将其传递给基类。

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 of object 类型指定将显示在列表开头的字符串、绑定或视图。
  • HeaderTemplate 类型的 DataTemplate 指定用于格式化 HeaderDataTemplate
  • 类型的对象指定将在列表末尾显示的字符串、绑定或视图。
  • FooterTemplate 类型的 DataTemplate 指定用于格式化 FooterDataTemplate

这些属性由 BindableProperty 对象支持,这意味着这些属性可以用作数据绑定的目标。

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

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

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

ListView 字符串标头的屏幕截图。

可以将 HeaderFooter 属性分别设置为视图。 这可以是单个视图,也可以是包含多个子视图的视图。 以下示例显示 HeaderFooter 属性,每个属性都设置为包含 Grid 对象的 Label 对象:

<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>

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

使用视图的 CollectionView 页眉和页脚的屏幕截图。使用视图的 CollectionView 页眉和页脚的屏幕截图。

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

<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 项之间。 可以通过将类型为 SeparatorVisibilitySeparatorVisibility 属性设置为 None 来更改此行为:

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

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

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

项目大小

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

RowHeight 属性可以设置为 int,表示 ListView 中每个项的高度,前提是 HasUnevenRowsfalse。 当 HasUnevenRows 设置为 true 时,ListView 中的每一项可以具有不同的高度。 每个项的高度将来自项的内容,因此每个项的大小将会随内容自动调整。

如果属性 HasUnevenRowstrue,则可以在运行时通过更改 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 的高度,而 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"
             xmlns:viewmodels="clr-namespace:ListViewDemos.ViewModels"
             x:Class="ListViewDemos.RightToLeftListPage"
             Title="Right to left list"
             FlowDirection="RightToLeft"
             x:DataType="viewmodels:MonkeysViewModel">
    <Grid Margin="20">
        <ListView ItemsSource="{Binding Monkeys}">
            ...
        </ListView>
    </Grid>
</ContentPage>

元素如果有父元素,则其默认 FlowDirectionMatchParent。 因此,ListViewGrid 继承 FlowDirection 属性值,而 Grid 又从 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 x:DataType="models:Animal">
            <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, static (AnimalsViewModel vm) => vm.Animals);
// ...

在 ListView 中,通过将其 ItemTemplate 属性设置为 DataTemplate 来定义每个项目的外观。 有关详细信息,请参阅定义项外观

以下屏幕截图显示了 ListView 展示分组数据:

ListView 中分组数据的屏幕截图。

注意

默认情况下,ListView 将在组标头中显示组名称。 可以通过自定义组标头来更改此行为。

自定义组标头

通过将 ListView.GroupHeaderTemplate 属性设置为 DataTemplate,即可自定义每个组标头的外观:

<ListView ItemsSource="{Binding Animals}"
          IsGroupingEnabled="True">
    <ListView.GroupHeaderTemplate>
        <DataTemplate x:DataType="models:AnimalGroup">
            <ViewCell>
                <Label Text="{Binding Name}"
                       BackgroundColor="LightGray"
                       FontSize="18"
                       FontAttributes="Bold" />
            </ViewCell>
        </DataTemplate>
    </ListView.GroupHeaderTemplate>
    ...
</ListView>

在此示例中,每个组标头都设置为一个用于显示组名的Label,并且还设置了其他外观属性。 以下屏幕截图显示了自定义组标头:

ListView 中自定义组标头的屏幕截图。ListView 中自定义组标头的屏幕截图。

重要

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

没有模板的组

ListView 可以正确显示分组数据,而无需将 ItemTemplate 属性设置为 DataTemplate

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

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

控制滚动

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

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

此外,ListView 还定义了一个 "Scrolled" 事件,用于指示发生了滚动。 ScrolledEventArgs 事件附带的 Scrolled 对象包含 ScrollXScrollY 属性。

检测滚动

ListView 定义了一个 Scrolled 事件,该事件被触发以指示发生了滚动。 ItemsViewScrolledEventArgs 类表示与 Scrolled 事件相关的对象,并定义以下属性:

  • ,类型为,表示滚动的 X 位置
  • ScrollY,类型为 double,表示滚动的 Y 位置。

以下 XAML 示例展示了一个 ListView,它为 Scrolled 事件设置了事件处理程序:

<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事件。

禁用滚动动画

当将项目滚动到可视区域时,会显示滚动动画。 但是,可以通过将 animated 方法的 ScrollTo 参数设置为 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);

中心

成员指示该项应滚动到视图的中心位置。

listView.ScrollTo(monkey, position: ScrollToPosition.Center, animate: true);

结束

ScrollToPosition.End 成员表示该项应滚动到视图的尽头。

listView.ScrollTo(monkey, position: ScrollToPosition.End, animate: true);

滚动条可见性

ListView 定义 HorizontalScrollBarVisibilityVerticalScrollBarVisibility 属性,这些属性由可绑定的属性支持。 这些属性用于获取或设置一个 枚举值,表示水平滚动条或垂直滚动条在何时可见。 ScrollBarVisibility 枚举定义了以下成员:

  • Default 指示平台的默认滚动条行为,并且是 HorizontalScrollBarVisibilityVerticalScrollBarVisibility 属性的默认值。
  • Always 表示滚动条将始终可见,无论内容是否完全适合视图。
  • Never 指示即使内容大小不适合视图,滚动条也将不可见。

添加上下文菜单

ListView 支持上下文菜单项,这些项被定义为 MenuItem 对象,并添加到每个项的 DataTemplate 中的 ViewCell.ContextActions 集合:

<ListView x:Name="listView"
          ItemsSource="{Binding Monkeys}">
    <ListView.ItemTemplate>
        <DataTemplate x:DataType="models:Monkey">
            <ViewCell>
                <ViewCell.ContextActions>
                    <MenuItem Text="Favorite"
                              Command="{Binding x:DataType='viewmodels:MonkeysViewModel', Source={RelativeSource AncestorType={x:Type viewmodels:MonkeysViewModel}}, Path=FavoriteCommand}"
                              CommandParameter="{Binding}" />
                    <MenuItem Text="Delete"
                              Command="{Binding x:DataType='viewmodels:MonkeysViewModel', Source={RelativeSource AncestorType={x:Type viewmodels:MonkeysViewModel}}, Path=DeleteCommand}"
                              CommandParameter="{Binding}" />
                </ViewCell.ContextActions>

                ...
            </ViewCell>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

在右键单击 ListView 中的某个项时,将显示 MenuItem 对象:

屏幕截图显示了 CollectionView 上下文菜单项。

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

下拉以刷新

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 x:DataType="models:Animal">
            <ViewCell>
                ...
            </ViewCell>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

在此示例中,当用户启动刷新时,将执行由 `RefreshCommand` 属性定义的命令ICommand,这将刷新当前显示的项目RefreshCommand。 刷新时会显示刷新可视化效果,该效果由动画进度圆组成。 IsRefreshing 属性的值指示刷新操作的当前状态。 触发刷新时,此属性将自动过渡到 true。 刷新完成后,应将属性重置为 false