You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 18:50:23