Flutter自定义加载屏幕指示器集成问题排查:加载动画在新项目正常但现有项目无显示
问题分析与解决方案
你遇到的核心问题是:直接在异步函数里调用Loader()并不会把这个加载动画渲染到UI上。Flutter的Widget必须被添加到Widget树中才能显示,而你当前的写法只是创建了一个Widget实例,但没有任何逻辑把它挂载到当前界面里——这和你之前用的UIUtils.showProcessIndicator()不一样,旧工具类应该是封装了Dialog或Overlay的逻辑来显示加载框。
下面给你两种可行的修复方案,你可以根据项目情况选择:
方案1:用Dialog封装加载动画(最接近你旧代码的用法)
首先把你的Loader抽成一个独立的Widget:
class LoaderWidget extends StatelessWidget { const LoaderWidget({super.key}); @override Widget build(BuildContext context) { return Center( child: Container( height: 130, width: 135, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(17), border: Border.all(color: Colors.blue) ), child: Column( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.center, children: const [ Text( "Loading...", style: TextStyle( fontSize: 13, letterSpacing: 2.1, color: Colors.blue, ), ), Padding( padding: EdgeInsets.all(3.0), child: Image.network( "https://cdn.dribbble.com/users/42716/screenshots/3913441/media/4ef7d67070fee7ab75948280f51d369f.gif", height: 100, ), ), ], ), ), ); } }
然后修改你的_vReport()函数,用showDialog来显示这个加载动画,并在API请求完成后关闭:
Future<List<VeReportModel>>? _vReport() async { debugPrint("Do not exploit this code $fDate &&&&&&&&& Thank you"); // 定义Dialog的上下文,用于后续关闭 late BuildContext dialogContext; try { if (await NetworkUtils.isNetworkAvailable()) { // 显示加载Dialog,禁止点击空白关闭 showDialog( context: context, barrierDismissible: false, builder: (ctx) { dialogContext = ctx; return const LoaderWidget(); }, ); ApiResponse? apiResponse = await RestApiClient(session: session) .vReport(fDate, tDate, spedLimit, stLimit, vId!); // 请求完成后关闭加载Dialog Navigator.of(dialogContext).pop(); Common.printWrapped('Map: _vEvent(): apiResponse=$apiResponse'); if (apiResponse!.successful && apiResponse.code == apiSuccessCode) { if (apiResponse.data != null) { List mapMessages = apiResponse.result; debugPrint("mapMessage $mapMessages"); return mapMessages .map((v) => VeReportModel.fromJson(v)) .toList(); } } } else { UIUtils.displayInternetError(context); } } catch (e,stack) { // 异常时也要确保关闭Dialog,避免加载框残留 if (dialogContext.mounted) { Navigator.of(dialogContext).pop(); } UIUtils.catchErrorMsg(context); debugPrint('Error While acknowledging v report : $e'); GeneralException.handleError(e, stack: stack, module: moduleVeReport); debugPrint('VeReport: Error while getting v list: $e'); } return []; }
方案2:在页面状态中控制加载动画(更优雅的UI集成)
如果你的页面是StatefulWidget,可以通过状态变量来控制加载动画的显示:
- 在页面State里添加状态变量:
bool _isLoading = false;
- 修改数据请求函数,更新加载状态:
Future<void> _fetchVReport() async { setState(() { _isLoading = true; }); try { if (await NetworkUtils.isNetworkAvailable()) { ApiResponse? apiResponse = await RestApiClient(session: session) .vReport(fDate, tDate, spedLimit, stLimit, vId!); Common.printWrapped('Map: _vEvent(): apiResponse=$apiResponse'); if (apiResponse!.successful && apiResponse.code == apiSuccessCode) { if (apiResponse.data != null) { List<VeReportModel> reportList = apiResponse.result .map((v) => VeReportModel.fromJson(v)) .toList(); // 处理获取到的数据,比如更新页面列表 } } } else { UIUtils.displayInternetError(context); } } catch (e,stack) { UIUtils.catchErrorMsg(context); debugPrint('Error While acknowledging v report : $e'); GeneralException.handleError(e, stack: stack, module: moduleVeReport); debugPrint('VeReport: Error while getting v list: $e'); } finally { // 无论请求成功或失败,都关闭加载状态 setState(() { _isLoading = false; }); } }
- 在页面的build方法中,根据
_isLoading条件渲染加载动画:
@override Widget build(BuildContext context) { return Scaffold( body: _isLoading ? const LoaderWidget() // 显示加载动画 : // 你的正常页面内容,比如列表、表单等 ); }
关键注意点
- 方案1中要确保在**所有代码分支(成功、失败、无网络)**都关闭Dialog,避免加载框一直停留在界面上;
- 方案2适合需要把加载动画嵌入页面布局的场景,比如覆盖在列表上方,视觉体验更流畅;
- 如果你用了Provider、Riverpod等状态管理库,可以把
_isLoading放到全局状态中,逻辑类似。
内容的提问来源于stack exchange,提问作者Piyush




