.NET MAUI Android端CollectionView搜索过滤卡顿问题求助
.NET MAUI Android端SearchBar过滤CollectionView卡顿问题解决
问题描述
.NET MAUI项目中,Android端使用SearchBar配合仅加载50条城市数据的CollectionView时,触发TextChanged事件过滤数据出现严重卡顿,无法正常输入。Windows端运行正常,所用Android设备性能良好,已尝试异步调用、切换为ListView等方案均无效。
现有代码
XAML代码
<StackLayout HorizontalOptions="FillAndExpand" Orientation="Vertical" Margin="5"> <Frame BackgroundColor="MintCream" Padding="10" CornerRadius="20"> <VerticalStackLayout> <SearchBar x:Name="FromSearchBar" TextChanged="FromSearchBar_TextChanged" Text="{Binding FromSearchText}" Placeholder="From City" HorizontalOptions="FillAndExpand" /> </VerticalStackLayout> </Frame> <CollectionView x:Name="FromColl" ItemsSource="{Binding Cities}" IsVisible="true"> <CollectionView.ItemTemplate> <DataTemplate> <HorizontalStackLayout Padding="10"> <Label Grid.Column="1" Text="{Binding FullCityName}" FontAttributes="Bold" /> </HorizontalStackLayout> </DataTemplate> </CollectionView.ItemTemplate> </CollectionView> </StackLayout>
后台C#代码
public MainPage() { InitializeComponent(); ViewModel = new NewItemViewModel(); this.BindingContext = ViewModel; } public NewItemViewModel ViewModel { get; set; } private void FromSearchBar_TextChanged(object sender, TextChangedEventArgs e) { var searchBar = (SearchBar)sender; ViewModel.Cities = new ObservableCollection<CityList>(CityRepository.GetAllFromDbAsync(searchBar.Text)); ViewModel.FromListViewVisible = true; }
数据库仓储代码
public static List<CityList> GetAllFromDbAsync(string str) { try { var rtdb = new RTDatabase(); if (string.IsNullOrEmpty(str)) return rtdb.GetItems().Result.Take(50).ToList(); else return rtdb.GetItems().Result.Where(a => a.CityName.ToLower().StartsWith(str.ToLower())).Take(50).ToList(); } catch (Exception ex) { LogError.log("CityRepository.cs - GetAllFromDbAsync - " + ex.Message); return new List<CityList>(); } } public Task<List<CityList>> GetItems() { return Database.Table<CityList>().ToListAsync(); }
问题根源
- UI线程阻塞:仓储方法中使用
.Result同步等待异步数据库操作,直接阻塞Android UI线程,导致输入卡顿。 - 频繁创建集合:每次搜索都新建
ObservableCollection,触发CollectionView全量重绘,开销极大。 - 低效数据过滤:先查询全量城市数据再在内存中过滤,即使只取50条,也浪费了IO和内存资源。
- 无防抖处理:TextChanged事件随用户输入频繁触发,短时间内多次执行数据库查询。
- 冗余布局层级:XAML中嵌套不必要的布局容器,增加Android渲染负担。
解决方案
1. 修复仓储异步方法,SQL层面过滤数据
public static async Task<List<CityList>> GetAllFromDbAsync(string str) { try { using var rtdb = new RTDatabase(); var query = rtdb.Database.Table<CityList>(); if (!string.IsNullOrEmpty(str)) { var lowerSearchStr = str.ToLower(); // 让SQLite直接执行过滤,减少内存处理 query = query.Where(a => a.CityName.ToLower().StartsWith(lowerSearchStr)); } // 先限制数量再查询,减少数据传输 return await query.Take(50).ToListAsync(); } catch (Exception ex) { LogError.log("CityRepository.cs - GetAllFromDbAsync - " + ex.Message); return new List<CityList>(); } }
2. 优化TextChanged事件,增加防抖+异步更新集合
private CancellationTokenSource _searchCancellationTokenSource; private async void FromSearchBar_TextChanged(object sender, TextChangedEventArgs e) { var searchBar = (SearchBar)sender; var searchText = searchBar.Text?.Trim() ?? string.Empty; // 取消上一次未完成的搜索任务,实现防抖 _searchCancellationTokenSource?.Cancel(); _searchCancellationTokenSource = new CancellationTokenSource(); try { // 300ms延迟,避免输入过程中频繁触发查询 await Task.Delay(300, _searchCancellationTokenSource.Token); var filteredCities = await CityRepository.GetAllFromDbAsync(searchText); // 更新现有集合而非新建,减少UI重绘 ViewModel.Cities.Clear(); foreach (var city in filteredCities) { ViewModel.Cities.Add(city); } ViewModel.FromListViewVisible = true; } catch (OperationCanceledException) { // 忽略取消的任务,无需处理 } catch (Exception ex) { // 按需添加异常处理逻辑 } }
3. ViewModel中初始化固定ObservableCollection
public class NewItemViewModel : INotifyPropertyChanged { // 初始化固定集合,后续仅更新内容 public ObservableCollection<CityList> Cities { get; } = new ObservableCollection<CityList>(); private bool _fromListViewVisible; public bool FromListViewVisible { get => _fromListViewVisible; set { _fromListViewVisible = value; OnPropertyChanged(); } } // 实现INotifyPropertyChanged接口 public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
4. 简化XAML布局,减少层级
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" Margin="5"> <Frame BackgroundColor="MintCream" Padding="10" CornerRadius="20" HorizontalOptions="FillAndExpand"> <SearchBar x:Name="FromSearchBar" TextChanged="FromSearchBar_TextChanged" Text="{Binding FromSearchText}" Placeholder="From City" HorizontalOptions="FillAndExpand" /> </Frame> <CollectionView x:Name="FromColl" ItemsSource="{Binding Cities}" IsVisible="{Binding FromListViewVisible}" VerticalOptions="FillAndExpand"> <CollectionView.ItemTemplate> <DataTemplate> <Label Text="{Binding FullCityName}" FontAttributes="Bold" Padding="10" /> </DataTemplate> </CollectionView.ItemTemplate> </CollectionView> </StackLayout>
关键优化点说明
- 用
await替代.Result,彻底避免UI线程阻塞。 - 防抖处理减少无效查询,降低资源消耗。
- SQL层面过滤数据,减少数据库IO和内存占用。
- 更新现有集合而非新建,让CollectionView仅渲染变化项。
- 简化布局层级,提升Android端渲染性能。
内容的提问来源于stack exchange,提问作者Sumit0501
相关产品推荐
相关产品推荐

