.NET MAUI中CollectionView重复添加项问题求助
解决.NET MAUI CollectionView滚动加载重复数据问题
异常现象
- 首次加载1、2、3、4、5项,后续每次滚动仍重复添加1、2、3、4、5项;
- 添加
Task.Delay(1000)后,会先重复添加1、2、3、4、5项,再加载6、7、8、9、10项等; - 使用
DisplayAlert()可避免重复添加,但这并非可行解决方案。
相关代码实现
CollectionView绑定代码
<CollectionView ItemsSource="{Binding Posts}" ItemTemplate="{StaticResource CommunityItemTemplateSelector}" RemainingItemsThresholdReachedCommand="{Binding PopulateCommand}" RemainingItemsThreshold="0"> <!-- Using DataTemplates to populate --> </CollectionView>
ViewModel代码
private CommunityService communityService { get; set; } private int start = 0; private int limit = 5; private List<CommunityItem> posts { get; set; } = new(); public ObservableRangeCollection<CommunityItem> Posts { get; private set; } = new(); public Command OnAppearingCommand { get; private set; } public Command PopulateCommand { get; private set; } public CommunityViewModel(CommunityService communityService) { this.communityService = communityService; OnAppearingCommand = new Command(Appearing); PopulateCommand = new Command(Populate); } private void Appearing() { Posts.Clear(); Populate(); } private async void Populate() { posts.Clear(); await Task.Delay(250); posts = await communityService.GetPosts(start, limit); start += limit; if (posts.Count > 0) Posts.AddRange(posts); }
CommunityService代码
public class CommunityService : BaseService { public List<CommunityItem> List = new(); public async Task<List<CommunityItem>> GetPosts(int start, int limit) { string range = "&start=" + start + "&limit=" + limit; try { var response = await http.GetAsync(this.uri + "/post" + range + this.key); if (response.IsSuccessStatusCode && response.Content is not null) List = await response.Content.ReadFromJsonAsync<List<CommunityItem>>(); // Somewhat solving the problem: await Task.Delay(1000); // Solving it completely for some reason, but shows an alert: await Application.Current.MainPage.DisplayAlert("Debug", "Waiting", "OK"); } catch (Exception e) { // Debug } return List; } }
根本原因
- 异步命令无状态追踪:
Populate方法使用async void,普通Command无法追踪异步操作的完成状态,导致CollectionView的加载命令被多次触发; - 线程安全问题:CommunityService中使用类级别的
List字段存储结果,异步请求时可能返回未更新的旧数据; - 无加载状态控制:未限制同一时间只能发起一次加载请求,
start变量还未更新就发起新请求,导致多次请求相同的分页参数。
解决方案
1. 改用异步命令追踪操作状态
使用AsyncRelayCommand(来自CommunityToolkit.Mvvm包)替代普通Command,它能正确处理异步方法的执行状态,避免重复触发。
2. 添加加载状态锁
在ViewModel中新增isLoading字段,确保同一时间只有一个加载请求在执行。
3. 修复Service的线程安全问题
移除类级别的List字段,每次请求创建新的列表对象,避免旧数据被复用。
修改后的代码
ViewModel(需安装CommunityToolkit.Mvvm NuGet包)
using CommunityToolkit.Mvvm.Input; private CommunityService communityService { get; set; } private int start = 0; private int limit = 5; public ObservableRangeCollection<CommunityItem> Posts { get; private set; } = new(); private bool isLoading; public IAsyncCommand OnAppearingCommand { get; private set; } public IAsyncCommand PopulateCommand { get; private set; } public CommunityViewModel(CommunityService communityService) { this.communityService = communityService; OnAppearingCommand = new AsyncRelayCommand(Appearing); PopulateCommand = new AsyncRelayCommand(Populate); } private async Task Appearing() { isLoading = true; Posts.Clear(); start = 0; await Populate(); isLoading = false; } private async Task Populate() { if (isLoading) return; isLoading = true; try { var newPosts = await communityService.GetPosts(start, limit); if (newPosts.Count > 0) { Posts.AddRange(newPosts); start += limit; } // 可选:当无新数据时,禁用加载命令 // else PopulateCommand.CanExecute(false); } finally { isLoading = false; } }
CommunityService
public class CommunityService : BaseService { public async Task<List<CommunityItem>> GetPosts(int start, int limit) { string range = $"&start={start}&limit={limit}"; var result = new List<CommunityItem>(); try { var response = await http.GetAsync($"{this.uri}/post{range}{this.key}"); if (response.IsSuccessStatusCode && response.Content is not null) result = await response.Content.ReadFromJsonAsync<List<CommunityItem>>() ?? new List<CommunityItem>(); } catch (Exception e) { // 可添加日志记录异常信息 } return result; } }
额外优化建议
- 在CollectionView的Footer中添加
ActivityIndicator,绑定isLoading字段,显示加载状态; - 当返回的新数据为空时,禁用
PopulateCommand,避免无效请求; - 可以添加下拉刷新功能,重置
start并重新加载数据。
内容的提问来源于stack exchange,提问作者user7862339
相关产品推荐
相关产品推荐

