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

加载数据时显示Activity Indicator的正确实现及问题排查

问题分析与解决方案

核心问题

  1. 构造函数同步触发加载:ViewModel构造函数直接调用LoadData,此时页面处于导航渲染阶段,UI线程被阻塞,IsBusy的UI更新无法及时生效,导致ActivityIndicator来不及显示。
  2. 线程调度与UI操作错误:用Task.Run包裹异步逻辑,且在后台线程直接更新Items(ObservableRangeCollection属于UI绑定集合,必须在主线程操作),同时MainThread.InvokeOnMainThreadAsync的使用方式繁琐,容易引发时序问题。
  3. 异步逻辑不规范:未遵循原生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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 17:13:28