6.3-集合内容类控件难点:CollectionView
一、基本使用(数据源在ViewModel中硬编码)
//①在Models文件夹下,新建Employee.cs文件,创建Employee类型
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public string Department { get; set; }
public string Portrait { get; set; }
public string Info { get; set; }
}
//②在ViewModels文件夹下,创建MainPageViewModel.cs
//头像图片复制到Resources/Images文件夹下
public partial class MainPageViewModel:ObservableObject
{
[ObservableProperty]
private ObservableCollection<Employee> employees;
public MainPageViewModel()
{
employees = new ObservableCollection<Employee>
{
new Employee{ Id=1,Name="zs",Department="行政部",Portrait="zs.png",Info="这是关于张三的介绍" },
new Employee{ Id=2,Name="ls",Department="行政部",Portrait="ls.png",Info="这是关于李四的介绍" },
new Employee{ Id=3,Name="ww",Department="营销部",Portrait="ww.png",Info="这是关于王五的介绍" },
new Employee{ Id=4,Name="zl",Department="营销部",Portrait="zl.png",Info="这是关于赵六的介绍" },
new Employee{ Id=5,Name="qq",Department="营销部",Portrait="qq.png",Info="这是关于钱七的介绍" }
};
}
}
//③MainPage.xaml使用CollectionView
<ContentPage
......
xmlns:vm="clr-namespace:MauiApp16.ViewModels">
<!--使用最简易的关联ViewModel方式-->
<ContentPage.BindingContext>
<vm:MainPageViewModel />
</ContentPage.BindingContext>
<!--ItemsSource属性为数据源-->
<CollectionView ItemsSource="{Binding Employees}">
<!--ItemTemplate属性定义外观,类型为DataTemplate-->
<!--DataTemplate为迭代的每一条数据项定义样式外观,类似于Vue中的v-for或Blazor中的foreach-->
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid ColumnDefinitions="Auto,Auto" RowDefinitions="Auto,Auto,Auto">
<Image
Grid.Row="0"
Grid.RowSpan="3"
Grid.Column="0"
Aspect="AspectFill"
HeightRequest="60"
Source="{Binding Portrait}"
WidthRequest="60" />
<Label
Grid.Row="0"
Grid.Column="1"
FontAttributes="Bold"
Text="{Binding Name}" />
<Label
Grid.Row="1"
Grid.Column="1"
Text="{Binding Department}" />
<Label
Grid.Row="2"
Grid.Column="1"
FontAttributes="Italic"
Text="{Binding Info}"
VerticalOptions="End" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</ContentPage>
二、外观设置
1、条件样式:迭代数据项时,根据不同条件,显示不同外观,类似于Vue的v-for中if、Blaozor的foreach中if
//①在Controls文件夹下,创建一个Selector类
public class EmployeeDataTemplateSelector : DataTemplateSelector
{
//实例化EmployeeDataTemplateSelector时,从外部赋值XingZhengBu和YingXiaoBu属性
public DataTemplate XingZhengBu { get; set; }
public DataTemplate YingXiaoBu { get; set; }
//参数item为CollectionView迭代数据项,此例为Employee类型对象
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
return ((Employee)item).Department.Contains("行政部")? XingZhengBu: YingXiaoBu;
}
}
//②在MainPage.xaml文件中使用
<ContentPage
......
xmlns:c="clr-namespace:MauiApp16.Controls"
xmlns:vm="clr-namespace:MauiApp16.ViewModels">
<ContentPage.BindingContext>
<vm:MainPageViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
<!-- 定义两个DataTemplate -->
<!-- 本例仅将部门Label设置为不同的颜色 -->
<DataTemplate x:Key="YingXiaoBu">
<Grid ColumnDefinitions="Auto,Auto" RowDefinitions="Auto,Auto,Auto">
......
<Label
Grid.Row="1"
Grid.Column="1"
Text="{Binding Department}"
TextColor="DarkBlue" />
......
</Grid>
</DataTemplate>
<DataTemplate x:Key="XingZhengBu">
<Grid ColumnDefinitions="Auto,Auto" RowDefinitions="Auto,Auto,Auto">
......
<Label
Grid.Row="1"
Grid.Column="1"
Text="{Binding Department}"
TextColor="DarkRed" />
......
</Grid>
</DataTemplate>
<!-- 将定义的两个DataTemplate传入DataTemplateSelector -->
<!-- 在DataTemplateSelector中,根据迭代的Item数据项条件,使用不同的DataTemplate -->
<c:EmployeeDataTemplateSelector
x:Key="EmployeeSelector"
XingZhengBu="{StaticResource XingZhengBu}"
YingXiaoBu="{StaticResource YingXiaoBu}" />
</ContentPage.Resources>
<!-- ItemTemplate使用DataTemplateSelector,将迭代数据项作为参数传入到Selector中 -->
<CollectionView ItemTemplate="{StaticResource EmployeeSelector}" ItemsSource="{Binding Employees}" />
</ContentPage>
2、排列方式:通过ItemsLayout设置垂直列表、水平列表、垂直网格、水平网格
1)垂直列表(如果不设置ItemsLayout,默认为垂直列表)
<!--简单设置ItemsLayout属性-->
<CollectionView ItemsSource="{Binding Employees}" ItemsLayout="VerticalList">
...
</CollectionView>
<!--通过元素属性设置ItemsLayout-->
<!--垂直/水平列表的类型为LinearItemsLayout-->
<CollectionView ItemsSource="{Binding Employees}">
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical" />
</CollectionView.ItemsLayout>
...
</CollectionView>
2)水平列表
<!--简单设置ItemsLayout属性-->
<CollectionView ItemsSource="{Binding Employees}" ItemsLayout="HorizontalList">
...
</CollectionView>
<!--通过元素属性设置ItemsLayout-->
<!--垂直/水平列表的类型为LinearItemsLayout-->
<CollectionView ItemsSource="{Binding Employees}">
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Horizontal" />
</CollectionView.ItemsLayout>
...
</CollectionView>
3)垂直网格
<!--通过ItemsLayout属性直接设置两列垂直网格-->
<CollectionView ItemsSource="{Binding Employees}"
ItemsLayout="VerticalGrid, 2">
......
</CollectionView>
<!--通过元素属性,设置两列垂直网格。网格的对象类型为GridItemsLayout-->
<CollectionView ItemsSource="{Binding Employees}">
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical"
Span="2" />
</CollectionView.ItemsLayout>
...
</CollectionView>
4)水平网格
<!--通过ItemsLayout属性直接设置两列水平网格-->
<CollectionView ItemsSource="{Binding Employees}"
ItemsLayout="HorizontalGrid, 2">
......
</CollectionView>
<!--通过元素属性,设置两列水平网格。网格的对象类型为GridItemsLayout-->
<CollectionView ItemsSource="{Binding Employees}">
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Horizontal"
Span="2" />
</CollectionView.ItemsLayout>
...
</CollectionView>
3、页眉页脚Header/Footer设置
1)页眉页脚设置为字符串
<!--设置Header属性和Footer属性-->
<CollectionView ItemsSource="{Binding Employees}"
Header="Employees"
Footer="2023">
...
</CollectionView>
2)页眉页脚中显示视图
<CollectionView ItemsSource="{Binding Employees}">
<CollectionView.Header>
<StackLayout BackgroundColor="Gray">
<Label
Margin="5"
FontAttributes="Bold"
FontSize="12"
Text="Employees" />
</StackLayout>
</CollectionView.Header>
<CollectionView.Footer>
<StackLayout BackgroundColor="AliceBlue">
<Label
Margin="5"
FontAttributes="Italic"
FontSize="10"
Text="2023" />
</StackLayout>
</CollectionView.Footer>
......
</CollectionView>
3)页眉页脚设置DataTemplate。使用了DataTemplate后,可以设置条件模板,可自行测试。
<CollectionView
Footer="{Binding .}"
Header="{Binding .}"
ItemsSource="{Binding Employees}">
<CollectionView.HeaderTemplate>
<DataTemplate>
<StackLayout BackgroundColor="Gray">
<Label
Margin="5"
FontAttributes="Bold"
FontSize="12"
Text="Employees" />
</StackLayout>
</DataTemplate>
</CollectionView.HeaderTemplate>
<CollectionView.FooterTemplate>
<DataTemplate>
<StackLayout BackgroundColor="AliceBlue">
<Label
Margin="5"
FontAttributes="Italic"
FontSize="10"
Text="2023" />
</StackLayout>
</DataTemplate>
</CollectionView.FooterTemplate>
......
</CollectionView>
4、数据项的间距。此时,ItemsLayout需要使用属性元素的方式进行设置(见第2项设置)
1)垂直/水平列表的数据项间距
<CollectionView ItemsSource="{Binding Employees}">
<CollectionView.ItemsLayout>
<LinearItemsLayout ItemSpacing="10" Orientation="Vertical" />
</CollectionView.ItemsLayout>
......
</CollectionView>
2)垂直/水平网格的Item数据项间距
<CollectionView ItemsSource="{Binding Employees}">
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical"
Span="2"
VerticalItemSpacing="20"
HorizontalItemSpacing="30" />
</CollectionView.ItemsLayout>
...
</CollectionView>
5、数据项大小调整的策略
<!--默认值MeasureAllItems,每一项都进行大小调整-->
<CollectionView ...
ItemSizingStrategy="MeasureAllItems">
...
</CollectionView>
<!--MeasureFirstItem值,后续数据项都根据第一项大小为准->
<CollectionView ...
ItemSizingStrategy="MeasureFirstItem">
...
</CollectionView>
6、数据项对齐方式
<!--默认为左对齐,通过在ContentPage上设置FlowDirection属性,修改为右对齐-->
<ContentPage
......
FlowDirection="RightToLeft">
<StackLayout Margin="20">
<CollectionView ItemsSource="{Binding Monkeys}">
...
</CollectionView>
</StackLayout>
</ContentPage>
7、动态调整数据项的大小
//XAML文件中,ImageButton定义Clicked事件
<ContentPage
......><Grid RowDefinitions="*,50"><CollectionView
......
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid ...>
<ImageButton
Clicked="ImageButton_Clicked"
HeightRequest="60"
WidthRequest="60"
Source="{Binding Portrait}"/>
......
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Grid>
</ContentPage>
//后台代码Clicked事件中,更改ImageButton的尺寸
private void ImageButton_Clicked(object sender, EventArgs e)
{
var imageButton = sender as ImageButton;
imageButton.HeightRequest = imageButton.WidthRequest = imageButton.HeightRequest.Equals(60) ? 100 : 60;
}
8、轻扫上下文菜单,结合SwipeView控件
<ContentPage
......>
<ContentPage.BindingContext>
<vm:MainPageViewModel />
</ContentPage.BindingContext>
<CollectionView
......>
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<!--使用SwipeView控件,包装DataTemplate对象-->
<SwipeView>
<!--设置左滑菜单,还可以设置右滑、上滑、下滑-->
<SwipeView.LeftItems>
<!--设置两个菜单,收藏和删除-->
<!--点击菜单,响应相应的命令,参数为当前行。设置为{Binding}或{Binding .}均可-->
<SwipeItems>
<SwipeItem
BackgroundColor="AliceBlue"
Command="{Binding Source={x:Reference collectionView}, Path=BindingContext.FavoriteCommand}"
CommandParameter="{Binding}"
Text="收藏" />
<SwipeItem
BackgroundColor="LightGray"
Command="{Binding Source={x:Reference collectionView}, Path=BindingContext.DeleteCommand}"
CommandParameter="{Binding .}"
Text="删除" />
</SwipeItems>
</SwipeView.LeftItems>
<Grid>
......
</Grid>
</SwipeView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</ContentPage>
//ViewModel中的命令(DeleteCommand)
[RelayCommand]
private void Delete(Employee employee)
{
employees.Remove(employee);
}
三、数据源设置
1、数据项选择控制
1)单选及设置预选
<ContentPage
......>
<ContentPage.BindingContext>
<vm:MainPageViewModel />
</ContentPage.BindingContext>
<VerticalStackLayout>
<!--
SelectionMode设置选择模式,Single-单选、Multiple-多选,None-禁用选择
SelectedItem设置初始预选项目,通过ViewModel设置。在后台代码中,将SelectedItem设置为null,则可以清除选择!
单选时,SelectedItem为双向绑定,Label的Text显示选定项目
SelectionChanged选择更改时触发的事件
-->
<Label Text="{Binding SelectedEmployee.Name}" />
<CollectionView
ItemsSource="{Binding Employees}"
SelectedItem="{Binding SelectedEmployee}"
SelectionChanged="CollectionView_SelectionChanged"
SelectionMode="Single">
<CollectionView.ItemTemplate>
......
</CollectionView.ItemTemplate>
</CollectionView>
</VerticalStackLayout>
</ContentPage>
//ViewModel设置初始预先项目SelectedEmployee
public partial class MainPageViewModel:ObservableObject
{
[ObservableProperty]
private ObservableCollection<Employee> employees;
[ObservableProperty]
private Employee selectedEmployee;
public MainPageViewModel()
{
employees = new ObservableCollection<Employee>
{
new Employee{ Id=1,Name="zs",Department="行政部",Portrait="zs.png",Info="这是关于张三的介绍" },
new Employee{ Id=2,Name="ls",Department="行政部",Portrait="ls.png",Info="这是关于李四的介绍" },
new Employee{ Id=3,Name="ww",Department="营销部",Portrait="ww.png",Info="这是关于王五的介绍" },
new Employee{ Id=4,Name="zl",Department="营销部",Portrait="zl.png",Info="这是关于赵六的介绍" },
new Employee{ Id=5,Name="qq",Department="营销部",Portrait="qq.png",Info="这是关于钱七的介绍" }
};
selectedEmployee = employees.Skip(2).FirstOrDefault();
}
}
2)多选及设置预选
<ContentPage
......>
<ContentPage.BindingContext>
<vm:MainPageViewModel />
</ContentPage.BindingContext>
<VerticalStackLayout>
<!--
SelectionMode设置选择模式,Single-单选、Multiple-多选
SelectedItems设置初始预选项目,通过ViewModel设置。多选时,SelectedItems为单向绑定
-->
<CollectionView
ItemsSource="{Binding Employees}"
SelectedItems="{Binding SelectedEmployees}"
SelectionMode="Multiple">
<CollectionView.ItemTemplate>
......
</CollectionView.ItemTemplate>
</CollectionView>
</VerticalStackLayout>
</ContentPage>
//ViewModel中设置SelectedEmployees
public partial class MainPageViewModel:ObservableObject
{
......
[ObservableProperty]
private ObservableCollection<Employee> selectedEmployees;
public MainPageViewModel()
{
......
selectedEmployees = new ObservableCollection<Employee> { employees[0], employees[3] };
}
}
3)设置选择颜色
<!--设置待定颜色稍显复杂,需要通过可视状态来设置-->
<!--Style的TargetType需设置为DataTemplate的根元素,本例中为Grid-->
<ContentPage ...>
<ContentPage.Resources>
<Style TargetType="Grid">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Selected">
<VisualState.Setters>
<Setter Property="BackgroundColor"
Value="LightSkyBlue" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
</ContentPage.Resources>
<StackLayout Margin="20">
<CollectionView ItemsSource="{Binding Monkeys}"
SelectionMode="Single">
.....
</CollectionView>
</StackLayout>
</ContentPage>
2、EmptyView:设置数据源不可用时,显示的替代内容
1)数据不可用时,显示字符串
<!--数据源EmptyEmployees为null,显示EmptyView的字符串值-->
<CollectionView ItemsSource="{Binding EmptyEmployees}" EmptyView="无数据显示">
......
</CollectionView>
2)数据不可用时,显示视图(ContentView)
<CollectionView ItemsSource="{Binding EmptyEmployees}">
<CollectionView.EmptyView>
<!--ContentView可以省略-->
<ContentView>
<StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
<Label
Margin="10,25,10,10"
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center"
Text="无数据显示" />
</StackLayout>
</ContentView>
</CollectionView.EmptyView>
......
</CollectionView>
3)运行时再确定数据不可用时显示的视图方式一:ContentView
<ContentPage
......>
<!--定义两个ContentView资源-->
<ContentPage.Resources>
<ContentView x:Key="EmptyViewA">
<StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
<Label FontSize="18" Text="无数据显示A" />
</StackLayout>
</ContentView>
<ContentView x:Key="EmptyViewB">
<StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
<Label FontSize="18" Text="无数据显示B" />
</StackLayout>
</ContentView>
</ContentPage.Resources>
<VerticalStackLayout>
<!--通过Swith的Toggled事件,后台代码更改CollectionView的EmptyView属性-->
<Switch Toggled="Switch_Toggled"/>
<CollectionView x:Name="collectionView" ItemsSource="{Binding EmptyEmployees}">
<CollectionView.ItemTemplate>
......
</CollectionView.ItemTemplate>
</CollectionView>
</VerticalStackLayout>
</ContentPage>
//Switch的Toggled事件上
private void Switch_Toggled(object sender, ToggledEventArgs e)
{
collectionView.EmptyView = e.Value ? Resources["EmptyViewA"] : Resources["EmptyViewB"];
}
4、下拉刷新?
5、增量加载?
四、列表滚动控制
1、滚动事件
<CollectionView Scrolled="CollectionView_Scrolled">
......
</CollectionView>
//ItemsViewScrolledEventArgs类型参数定义了丰富的属性,包括:
//HorizontalDelta/VerticalDelta:水平/垂直滚动量
//HorizontalOffset/VerticalOffset:相对于原点的水平/垂直偏移量
//FirstVisibleItemIndex/LastVisibleItemIndex/CenterItemIndex:列表中可见的第一项/最后一项/中间项索引
private async void CollectionView_Scrolled(object sender, ItemsViewScrolledEventArgs e)
{
......
}
2、代码控件滚动
1)滚动到索引
//滚动到索引为5的数据项,并将数据项显示在列表可视窗的开始位置
collectionView.ScrollTo(5,position:ScrollToPosition.Start);
//分组数据时,滚动到第2组,索引为1的数据项
collectionView.ScrollTo(2, 1);
2)滚动到Item数据项
//获取当前页面的BindingContext,并转化为页面对应的ViewModel对象
var viewModel = BindingContext as MainPageViewModel;
//筛选ViewModel中的Employees数据集合,并选取Name为qq的第一条数据
var person = viewModel.Employees.FirstOrDefault(e=>e.Name=="qq");
//滚动到这条数据项,在列表窗口的开始位置显示,并禁用滚动动画
collectionView.ScrollTo(person,position:ScrollToPosition.Start,animate:false);
//分组数据滚动
GroupedAnimalsViewModel viewModel = BindingContext as GroupedAnimalsViewModel;
AnimalGroup group = viewModel.Animals.FirstOrDefault(a => a.Name == "Monkeys");
Animal monkey = group.FirstOrDefault(m => m.Name == "Proboscis Monkey");
collectionView.ScrollTo(monkey, group);
五、数据分组显示 :
总体分为两步:一、设置分组数据源;二、在XAML中显示分组;三、设置页眉、页脚、空组显示等,可详见文档。