Xamarin Forms:如何将ObservableCollection属性绑定到CarouselView实现关卡选择器
问题分析与解决办法
你的核心错误在于CarouselView的数据源和ItemTemplate的对应关系搞反了:
- 当前你的
ItemsSource="{Binding Cards}"绑定的是单个LevelCard的集合,所以每个轮播项(DataTemplate)的上下文是单个LevelCard对象,但你却在模板里试图绑定card1、card2这类变量——这些变量只存在于ViewModel的初始化代码里,并不是LevelCard的属性,系统自然会去父ViewModel里查找,导致绑定失败。
你需要的是每个轮播项显示6个LevelCard,所以必须先把关卡数据按6个一组进行分组,再绑定分组后的集合到CarouselView。
步骤1:调整ViewModel,实现关卡分组
修改LevelSelectorPopUpModel,将单个LevelCard集合改为分组后的集合(每组6个):
public class LevelSelectorPopUpModel : BaseViewModel { ObservableCollection<ObservableCollection<LevelCard>> cardGroups = new ObservableCollection<ObservableCollection<LevelCard>>(); public ObservableCollection<ObservableCollection<LevelCard>> CardGroups { get => cardGroups; } int game = 1; int totalGames; public LevelSelectorPopUpModel() { Initialize(); } async void Initialize() { totalGames = await LevelReader.GetTotalGames(); InitializeWidthHeightProperties(); InitializeCardGroups(); } void InitializeCardGroups() { // 按6个一组创建分组 while (game <= totalGames) { var group = new ObservableCollection<LevelCard>(); // 每组最多6个 for (int i = 0; i < 6 && game <= totalGames; i++) { var card = new LevelCard(); card.Game = game; // 这里可以设置其他属性,比如Score、IsCompleted group.Add(card); game++; } cardGroups.Add(group); } } }
另外,为了支持属性更新(比如星级变化),LevelCard需要实现INotifyPropertyChanged(如果你的BaseViewModel已经封装了这个逻辑,可以让LevelCard继承它):
public class LevelCard : BaseViewModel { int game; public int Game { get => game; set => SetProperty(ref game, value); } int score; public int Score { get => score; set => SetProperty(ref score, value); } bool isCompleted; public bool IsCompleted { get => isCompleted; set => SetProperty(ref isCompleted, value); } }
步骤2:修改XAML,绑定分组后的集合
调整CarouselView的ItemsSource为分组后的CardGroups,然后在ItemTemplate里绑定组内的每个LevelCard属性:
<CarouselView ItemsSource="{Binding CardGroups}" HorizontalScrollBarVisibility="Always" WidthRequest="{Binding Width}" HeightRequest="600" Margin="10, 0, 10, 0" BackgroundColor="Red" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand"> <CarouselView.ItemTemplate> <DataTemplate> <Grid RowSpacing="50" VerticalOptions="CenterAndExpand"> <Grid.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <StackLayout Orientation="Horizontal" WidthRequest="600" HeightRequest="250" BackgroundColor="Orange" VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand" Margin="0, 10, 0, 0"> <!-- 绑定组内第0个LevelCard的Game属性 --> <Label Text="{Binding [0].Game, FallbackValue='-'}" BackgroundColor="Purple" WidthRequest="200" HeightRequest="200" /> <!-- 绑定组内第1个LevelCard的Game属性 --> <Label Text="{Binding [1].Game, FallbackValue='-'}" BackgroundColor="Purple" WidthRequest="200" HeightRequest="200" /> <!-- 绑定组内第2个LevelCard的Game属性 --> <Label Text="{Binding [2].Game, FallbackValue='-'}" BackgroundColor="Purple" WidthRequest="200" HeightRequest="200" /> </StackLayout> <StackLayout Orientation="Horizontal" WidthRequest="600" HeightRequest="250" BackgroundColor="Orange" Grid.Row="1" VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand" Margin="0, 0, 0, 10"> <!-- 绑定组内第3个LevelCard的Game属性 --> <Label Text="{Binding [3].Game, FallbackValue='-'}" BackgroundColor="Purple" WidthRequest="200" HeightRequest="200" /> <!-- 绑定组内第4个LevelCard的Game属性 --> <Label Text="{Binding [4].Game, FallbackValue='-'}" BackgroundColor="Purple" WidthRequest="200" HeightRequest="200" /> <!-- 绑定组内第5个LevelCard的Game属性 --> <Label Text="{Binding [5].Game, FallbackValue='-'}" BackgroundColor="Purple" WidthRequest="200" HeightRequest="200" /> </StackLayout> </Grid> </DataTemplate> </CarouselView.ItemTemplate> </CarouselView>
关键说明
- 现在每个轮播项的上下文是一组6个LevelCard的集合,所以用
[索引]可以直接访问组内的第N个卡片 FallbackValue='-'用于处理最后一组不足6个的情况(比如总关卡数不是6的倍数时)- 如果你要显示星级,只需要把
Text="{Binding [0].Game}"改成Text="{Binding [0].Score}"即可,后续更新LevelCard的Score属性时,UI会自动刷新(前提是LevelCard实现了INotifyPropertyChanged)
内容的提问来源于stack exchange,提问作者ThisQRequiresASpecialist
相关产品推荐
相关产品推荐

