.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
相关产品推荐
相关产品推荐

