Flutter中用ModalProgressHUD配合ListView.builder读取Firebase数据时显示加载框
解决Flutter列表页面ModalProgressHUD加载指示器不生效的问题
看起来你遇到的核心问题是**showSpinner变量的状态更新没有触发UI重建**,再加上ListView.builder的渲染时机和Provider状态更新的配合问题,导致加载指示器没按预期工作。咱们一步步来修复:
问题根源分析
showSpinner未通过setState更新:你把showSpinner声明为类级变量,但修改它时没有调用setState((){}),Flutter无法感知状态变化,自然不会刷新UI来隐藏加载指示器。_refreshList未正确处理异步逻辑:expenseNotifier.getExpenses()应该是异步操作,但你没有在它完成后更新showSpinner的状态。- Consumer内的空状态判断有误:
expense是Provider注入的实例,永远不会为null,应该判断expense.expenseList.isEmpty来显示空页面。
修复步骤及完整代码
1. 调整showSpinner的状态更新逻辑
每次触发数据加载时,先通过setState将showSpinner设为true,等异步加载完成后再设为false。
2. 修正_refreshList方法
确保getExpenses是异步方法,并且在它完成后更新状态。
3. 优化空状态判断
把expense == null改为expense.expenseList.isEmpty,让空状态能正确触发。
以下是修正后的完整代码:
bool showSpinner = true; @override Widget build(BuildContext context) { ExpenseNotifier expenseNotifier = Provider.of<ExpenseNotifier>(context); Future<void> _refreshList() async { // 开始刷新时显示加载指示器 setState(() { showSpinner = true; }); // 等待数据加载完成 await expenseNotifier.getExpenses(expenseNotifier); // 加载完成后隐藏指示器 setState(() { showSpinner = false; }); } // 页面渲染完成后,判断是否需要自动加载数据 WidgetsBinding.instance.addPostFrameCallback((_) { if (expenseNotifier.expenseList.isEmpty) { _refreshList(); } else { setState(() { showSpinner = false; }); } }); return Scaffold( body: ModalProgressHUD( inAsyncCall: showSpinner, child: RefreshIndicator( onRefresh: _refreshList, child: Consumer<ExpenseNotifier>( builder: (context, expense, child) { // 判断列表是否为空,而非expense实例是否为null return expense.expenseList.isEmpty ? Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ PaddingClass(bodyImage: 'images/empty.png'), SizedBox(height: 20.0), Text( 'You don\'t have any expenses', style: kLabelTextStyle, ), ], ) : ListView.separated( itemBuilder: (context, int index) { var myExpense = expense.expenseList[index]; return Card( elevation: 8.0, color: Colors.white70, child: Column( mainAxisAlignment: MainAxisAlignment.start, children: <Widget>[ RegularExpenseTextPadding( regText: '${_formattedDate(myExpense.updatedAt)}', ), Container( margin: EdgeInsets.all(20.0), padding: const EdgeInsets.all(15.0), decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(5.0)), border: Border.all(color: kThemeStyleBorderHighlightColour), ), child: Row( children: <Widget>[ Expanded( flex: 5, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Text( '${myExpense.amount}', style: kRegularTextStyle, ), SizedBox(height: 20.0), Text( myExpense.description, style: kRegularTextStyle, ), ], ), ), Expanded( flex: 1, child: GestureDetector( onTap: () { expenseNotifier.currentExpense = expenseNotifier.expenseList[index]; Navigator.of(context).push( MaterialPageRoute(builder: (BuildContext context) { return ExpenseDetailsScreen(); }), ); }, child: Icon( FontAwesomeIcons.caretDown, color: kThemeIconColour, ), ), ), ], ), ), ], ), ); }, separatorBuilder: (BuildContext context, int index) { return SizedBox(height: 20.0); }, itemCount: expense.expenseList.length, ); }, ), ), ), ); }
额外注意事项
- 确保
ExpenseNotifier.getExpenses()是异步方法,并且在数据加载完成后调用了notifyListeners(),这样Consumer才能感知到expenseList的变化。 - 页面初始化时的
addPostFrameCallback用来避免重复加载数据,也能确保页面渲染完成后再处理状态。
内容的提问来源于stack exchange,提问作者hermie_brown
相关产品推荐
相关产品推荐

