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

如何在.NET Maui中实现商品分类联动滚动布局?

实现.NET Maui商品页面分类栏与商品列表联动方案

针对你的需求,我们可以通过独立分类栏+商品列表滚动监听+双向联动逻辑来实现,具体步骤如下:

1. 数据结构准备

先定义基础模型和视图模型,提前计算分类与商品的索引映射,方便后续联动:

// 分类模型
public class Category
{
    public int Id { get; set; }
    public string Name { get; set; }
    public bool IsSelected { get; set; } // 标记当前选中状态
}

// 商品模型
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int CategoryId { get; set; } // 关联分类ID
}

// 标签页对应的视图模型
public class ProductTabViewModel : ObservableObject
{
    private Category _selectedCategory;
    public List<Category> Categories { get; set; } = new();
    public List<Product> AllProducts { get; set; } = new();
    public Category SelectedCategory
    {
        get => _selectedCategory;
        set
        {
            // 先取消原有选中状态
            if (_selectedCategory != null)
                _selectedCategory.IsSelected = false;
            value.IsSelected = true;
            SetProperty(ref _selectedCategory, value);
        }
    }

    // 存储每个分类对应的第一个商品在列表中的索引
    public Dictionary<int, int> CategoryStartIndexes { get; private set; } = new();

    // 初始化索引映射
    public void InitCategoryIndexes()
    {
        CategoryStartIndexes.Clear();
        int currentIndex = 0;
        foreach (var category in Categories)
        {
            CategoryStartIndexes[category.Id] = currentIndex;
            currentIndex += AllProducts.Count(p => p.CategoryId == category.Id);
        }
        // 默认选中第一个分类
        if (Categories.Any())
            SelectedCategory = Categories.First();
    }
}

2. 页面布局实现

每个标签页的布局分为顶部横向滚动分类栏和下方商品列表两部分:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:YourNamespace"
             x:Class="YourNamespace.ProductTabPage">
    <ContentPage.BindingContext>
        <local:ProductTabViewModel />
    </ContentPage.BindingContext>
    <ContentPage.Resources>
        <!-- 布尔值转颜色转换器 -->
        <local:BoolToColorConverter x:Key="BoolToColorConverter" />
    </ContentPage.Resources>

    <VerticalStackLayout>
        <!-- 顶部分类栏 -->
        <ScrollView x:Name="CategoryScrollView" Orientation="Horizontal" BackgroundColor="#F5F5F5" Padding="10">
            <HStack Spacing="8">
                <Label 
                    Text="{Binding Name}"
                    Padding="12,6"
                    CornerRadius="16"
                    BackgroundColor="{Binding IsSelected, Converter={StaticResource BoolToColorConverter}, ConverterParameter='#2196F3,#FFFFFF'}"
                    TextColor="{Binding IsSelected, Converter={StaticResource BoolToColorConverter}, ConverterParameter='#FFFFFF,#333333'}"
                    FontAttributes="{Binding IsSelected, Converter={StaticResource BoolToFontAttributeConverter}}"
                    x:DataType="local:Category"
                    x:Name="CategoryLabel_{Binding Id}"
                    >
                    <Label.GestureRecognizers>
                        <TapGestureRecognizer 
                            Command="{Binding Source={RelativeSource AncestorType={x:Type local:ProductTabViewModel}}, Path=JumpToCategoryCommand}"
                            CommandParameter="{Binding .}" />
                    </Label.GestureRecognizers>
                </Label>
            </HStack>
        </ScrollView>

        <!-- 商品列表 -->
        <CollectionView 
            x:Name="ProductCollectionView"
            ItemsSource="{Binding AllProducts}"
            Scrolled="ProductCollectionView_Scrolled"
            VerticalOptions="FillAndExpand"
            Margin="0,10">
            <CollectionView.ItemTemplate>
                <DataTemplate x:DataType="local:Product">
                    <Frame Margin="10,5" Padding="15" CornerRadius="8" ShadowColor="#EEEEEE" ShadowRadius="4">
                        <VerticalStackLayout>
                            <Label Text="{Binding Name}" FontSize="16" FontAttributes="Bold" />
                            <!-- 其他商品信息控件 -->
                        </VerticalStackLayout>
                    </Frame>
                </DataTemplate>
            </CollectionView.ItemTemplate>
        </CollectionView>
    </VerticalStackLayout>
</ContentPage>

3. 核心联动逻辑实现

3.1 滚动商品列表时同步分类栏

监听商品列表的滚动事件,获取当前可见的第一个商品,匹配对应分类并更新分类栏状态:

private void ProductCollectionView_Scrolled(object sender, ItemsViewScrolledEventArgs e)
{
    var viewModel = BindingContext as ProductTabViewModel;
    if (viewModel == null || !viewModel.AllProducts.Any()) return;

    // 获取第一个可见商品的索引
    var firstVisibleIndex = e.FirstVisibleItemIndex;
    if (firstVisibleIndex < 0 || firstVisibleIndex >= viewModel.AllProducts.Count) return;

    // 匹配当前商品所属分类
    var currentProduct = viewModel.AllProducts[firstVisibleIndex];
    var targetCategory = viewModel.Categories.First(c => c.Id == currentProduct.CategoryId);

    // 更新选中状态并滚动分类栏到对应位置
    if (viewModel.SelectedCategory != targetCategory)
    {
        viewModel.SelectedCategory = targetCategory;
        var targetLabel = CategoryScrollView.FindByName<Label>($"CategoryLabel_{targetCategory.Id}");
        if (targetLabel != null)
        {
            CategoryScrollView.ScrollToAsync(targetLabel, ScrollToPosition.Center, true);
        }
    }
}

3.2 点击分类栏跳转到对应商品位置

在视图模型中添加命令,点击分类时直接滚动到该分类的第一个商品:

[RelayCommand]
private void JumpToCategory(Category category)
{
    if (CategoryStartIndexes.TryGetValue(category.Id, out int startIndex))
    {
        ProductCollectionView.ScrollTo(startIndex, position: ScrollToPosition.Start, animate: true);
        SelectedCategory = category;
    }
}

4. 辅助转换器实现

添加两个基础转换器,用于分类标签的样式切换:

// 布尔值转颜色转换器
public class BoolToColorConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        bool isSelected = (bool)value;
        var colors = parameter.ToString().Split(',');
        return isSelected ? Color.FromArgb(colors[0].Trim()) : Color.FromArgb(colors[1].Trim());
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

// 布尔值转字体属性转换器
public class BoolToFontAttributeConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return (bool)value ? FontAttributes.Bold : FontAttributes.None;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

5. 性能优化提示

  • 滚动事件可能频繁触发,可添加防抖逻辑(例如用Timer延迟50ms执行匹配逻辑)
  • 提前预计算分类的起始索引,避免每次滚动时遍历商品集合
  • 分类标签的FindByName操作可提前缓存,减少控件查找开销

内容的提问来源于stack exchange,提问作者Thomas Carlton

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 11:35:20