加载数据时显示Activity Indicator的正确实现及问题排查
问题分析与解决方案
核心问题
- 构造函数同步触发加载:ViewModel构造函数直接调用
LoadData,此时页面处于导航渲染阶段,UI线程被阻塞,IsBusy的UI更新无法及时生效,导致ActivityIndicator来不及显示。 - 线程调度与UI操作错误:用
Task.Run包裹异步逻辑,且在后台线程直接更新Items(ObservableRangeCollection属于UI绑定集合,必须在主线程操作),同时MainThread.InvokeOnMainThreadAsync的使用方式繁琐,容易引发时序问题。 - 异步逻辑不规范:未遵循原生async/await最佳实践,导致线程调度混乱。
正确实现步骤
1. 修改ViewModel代码
[ObservableProperty] bool _isBusy; [ObservableProperty] string _loadingText; public ObservableRangeCollection<TestModel> Items { get; set; } = new ObservableRangeCollection<TestModel>(); private readonly IRepository _repository; private IEnumerable<TestModel> _allItems; // 用RelayCommand定义异步加载命令(需依赖CommunityToolkit.Mvvm) [RelayCommand] private async Task LoadDataAsync() { IsBusy = true; LoadingText = "Loading..."; Items.Clear(); try { // 直接异步调用,await后自动回到主线程,无需额外线程切换 _allItems = await _repository.GetAllAsync(); Items.AddRange(_allItems); } catch (Exception ex) { // 建议添加错误处理,比如弹窗提示 // await Shell.Current.DisplayAlert("错误", ex.Message, "确定"); } finally { IsBusy = false; } } public TestViewModel(IRepository repository) { _repository = repository; // 构造函数中不再触发加载逻辑 } // 若使用Maui导航生命周期,可实现INavigatedAware接口自动触发加载 public async void OnNavigatedTo(INavigationParameters parameters) { await LoadDataAsync(); }
2. 修改View代码
如果不使用生命周期接口,可通过页面Appearing事件触发加载命令:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="你的命名空间.TestPage" xmlns:vm="clr-namespace:你的命名空间.ViewModels" xmlns:converters="clr-namespace:你的命名空间.Converters" Appearing="ContentPage_Appearing"> <ContentPage.Resources> <converters:InvertedBoolConverter x:Key="InvertedBoolConverter"/> </ContentPage.Resources> <ContentPage.BindingContext> <vm:TestViewModel/> </ContentPage.BindingContext> <Grid> <!-- 数据集合视图 --> <CollectionView IsVisible="{Binding IsBusy, Converter={StaticResource InvertedBoolConverter}}" ItemsSource="{Binding Items}" SelectionMode="None"> <CollectionView.ItemTemplate> <DataTemplate> <Label Text="{Binding Name}"/> </DataTemplate> </CollectionView.ItemTemplate> </CollectionView> <!-- 加载指示器区域 --> <VerticalStackLayout IsVisible="{Binding IsBusy}" HorizontalOptions="Center" Spacing="6" VerticalOptions="Center"> <ActivityIndicator HeightRequest="44" HorizontalOptions="Center" IsRunning="{Binding IsBusy}" Scale="{OnPlatform iOS=1.3, Android=1.0}" WidthRequest="44" Color="{DynamicResource PrimaryColor}" /> <Label FontSize="14" HorizontalOptions="Center" HorizontalTextAlignment="Center" LineBreakMode="WordWrap" Text="{Binding LoadingText}" TextColor="{DynamicResource PrimaryColor}" /> </VerticalStackLayout> </Grid> </ContentPage>
对应的页面后台代码:
private async void ContentPage_Appearing(object sender, EventArgs e) { if (BindingContext is TestViewModel vm) { await vm.LoadDataAsyncCommand.ExecuteAsync(null); } }
关键优化点
- 规范异步逻辑:用
async Task替代Task.Run,避免不必要的线程切换,await后自动回到主线程,安全更新UI绑定属性和集合。 - 延迟加载时机:将加载逻辑从构造函数移到页面
Appearing事件或导航生命周期方法,确保页面渲染完成后再触发加载,此时IsBusy的UI更新能及时生效,ActivityIndicator正常显示。 - 简化UI操作:无需手动调用
MainThread.InvokeOnMainThreadAsync,async/await模式自动处理线程上下文切换。
内容的提问来源于stack exchange,提问作者Zoltán
相关产品推荐
相关产品推荐

