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

.NET MAUI中用ObservableCollection填充CollectionView时跳回顶部问题

CollectionView绑定ObservableCollection加载更多后跳回顶部的问题解决

问题现象

  • 使用ObservableCollection绑定MAUI的CollectionView展示SQLite数据,滚动触发加载更多后,页面会跳回顶部(多数情况是第三个项显示一半,并非完全顶部)
  • 存在重复填充问题:设置每次加载7条数据,到达阈值后会连续触发两次加载,各填充7条
  • 此前从API直接获取数据时功能正常,改用SQLite数据源后出现该问题
  • 尝试设置ItemsUpdatingScrollMode="KeepScrollOffset",问题依然存在

解决方案

将外层的<VerticalStackLayout>替换为<Grid>即可解决跳回顶部的问题,推测是VerticalStackLayout与CollectionView的滚动行为冲突导致布局重排,进而引发滚动偏移异常。

修改后的核心布局示例:

<Grid>
    <Image Style="{StaticResource Header_Image}"/>
    <CollectionView ItemsSource="{Binding Items, Mode=OneTime}"
                    RemainingItemsThresholdReachedCommand="{Binding PopulateCommand}"
                    ItemsUpdatingScrollMode="KeepScrollOffset"
                    RemainingItemsThreshold="1">
        <!-- 原有ItemTemplate内容保持不变 -->
    </CollectionView>
</Grid>

额外优化:解决重复加载问题

当前Populate方法未处理并发触发的情况,导致RemainingItemsThresholdReachedCommand被多次执行时重复加载数据。可以通过判断Loading状态来避免重复执行:

public void Populate()
{
    if (repository is null || Loading) // 增加Loading状态判断
        return;

    Loading = true;

    try
    {
        var allItems = repository.ReadAll();
        if (allItems.Count > Items.Count)
        {
            var newItems = allItems.Skip(Items.Count).Take(limit);
            foreach (var item in newItems)
            {
                Items.Add(item);
            }
        }
    }
    finally
    {
        Loading = false;
    }
}

相关代码参考

原视图代码(问题版本)

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:m="clr-namespace:NewProject.Models"
             xmlns:vm="clr-namespace:NewProject.ViewModels"
             xmlns:toolkit="clr-namespace:CommunityToolkit.Maui;assembly=CommunityToolkit.Maui"
             x:Class="NewProject.Views.ItemsPage"
             x:DataType="vm:ItemsViewModel"
             Title="{Binding Title}">
    <!-- Replaced VerticalStackLayout with Grid to solve the issue! -->
    <VerticalStackLayout>
        <Image Style="{StaticResource Header_Image}"/>
        <CollectionView ItemsSource="{Binding Items, Mode=OneTime}"
                        RemainingItemsThresholdReachedCommand="{Binding PopulateCommand}"
                        ItemsUpdatingScrollMode="KeepScrollOffset"
                        RemainingItemsThreshold="1">
            <CollectionView.ItemTemplate>
                <DataTemplate x:DataType="m:Item">
                    <Frame Style="{StaticResource ItemList_Item}">
                        <Grid ColumnDefinitions="5*,*,*">
                            <Label Style="{StaticResource ItemList_ItemName}"
                                   Text="{Binding Name, Mode=OneTime}"
                                   Grid.Column="0" Grid.Row="0"/>
                        </Grid>
                    </Frame>
                </DataTemplate>
            </CollectionView.ItemTemplate>
        </CollectionView>
    </VerticalStackLayout>
</ContentPage>

ViewModel代码

public class ItemsViewModel : BaseViewModel
{
    private IItemRepository repository;
    private int limit = 7;

    public Command PopulateCommand { get; }

    public ObservableCollection<Item> Items { get; } = new();

    public ItemsViewModel(IItemRepository repository)
    {
        this.repository = repository;

        PopulateCommand = new Command(Populate);

        Populate();
    }

    public void Populate()
    {
        if (repository is not null)
        {
            this.Loading = true;

            if (repository.ReadAll().Count > Items.Count)
            {
                var items= repository.ReadAll();

                foreach (var item in items.Skip(Items.Count).Take(limit))
                {
                    Items.Add(item);
                }
            }

            this.Loading = false;
        }
    }
}

仓储接口代码

public interface IItemRepository
{
    void Create(Item item);
    Item Read(int ItemId);
    void Update(Item item);
    void Delete(int ItemId);
    List<Item> ReadAll();
}

仓储实现代码

public class ItemRepository : IItemRepository
{
    private readonly SQLiteConnection conn;

    public ItemRepository()
    {
        if (conn is not null)
        {
            return;
        }

        var path = Path.Combine(FileSystem.AppDataDirectory, "item.db");
        conn = new SQLiteConnection(path);

        conn.CreateTable<Item>();
    }

    public void Create(Item item)
    {
        conn.Insert(item);
    }

    public Item Read(int ItemId)
    {
        return conn.Find<Item>(ItemId);
    }

    public void Update(Item item)
    {
        conn.Update(item);
    }

    public void Delete(int ItemId)
    {
        conn.Delete(ItemId);
    }

    public List<Item> ReadAll()
    {
        return conn.Table<Item>().OrderBy(item => item.Name).ToList();
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 17:12:02