Flutter中ShowModalBottomSheet内文本框无法更新问题求助
解决Flutter ShowModalBottomSheet编辑弹窗文本自动恢复原始值的问题
问题原因
你遇到的核心问题是:每次触发setState(比如点击按钮、切换Tab)时,editExpenseTabContent方法会重新执行,而方法内的editLabel.text = record.label;等代码会将控制器重置为原始记录的值,直接覆盖用户输入的内容。
因为StatefulBuilder的setState会触发子Widget重建,而editExpenseTabContent作为TabBarView的子Widget,每次重建都会重新给控制器赋值,导致修改后的内容自动恢复成原始值。
解决方案
将控制器的初始值设置逻辑移到editBottomSheet方法内部,只在弹窗打开时初始化一次,避免每次Widget重建都覆盖用户输入。同时将状态变量移到弹窗内部,避免全局状态污染。
修改后的代码示例
1. 更新editBottomSheet方法
void editBottomSheet(BudgetRecord record) { int initialTabIndex = (record.type.toLowerCase() == 'expense') ? 0 : 1; // 每次打开弹窗时创建独立的控制器和状态变量,避免全局状态冲突 final editLabel = TextEditingController(text: record.label); final editAmount = TextEditingController(text: record.amount); final editDescription = TextEditingController(text: record.description); bool editTogle = record.done; bool _editAdvanceUpArrow = false; bool _editTextFieldUpArrow = false; showModalBottomSheet( isScrollControlled: true, backgroundColor: Colors.white, elevation: 15, context: context, builder: (BuildContext context) { return StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return DefaultTabController( length: 2, initialIndex: initialTabIndex, child: SingleChildScrollView( padding: EdgeInsets.only( top: 10, bottom: MediaQuery.of(context).viewInsets.bottom, ), child: Column( children: [ const SizedBox(height: 10), Image.asset('assets/bottomSheetBar.png'), const SizedBox(height: 20), TabBar( indicatorSize: TabBarIndicatorSize.tab, indicatorColor: AppColors.mainColor, unselectedLabelColor: AppColors.lightTextColor, tabs: [ Tab( child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset('assets/expense_tab_logo.png'), const SizedBox(width: 10), const Text( 'Expense', style: TextStyle(color: AppColors.expenseColor), ), ], ), ), Tab( child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset('assets/income_tab_logo.png'), const SizedBox(width: 10), const Text( 'Income', style: TextStyle(color: AppColors.incomeColor), ), ], ), ), ], ), SizedBox( height: 440, child: TabBarView( children: [ editExpenseTabContent(setState, record, editLabel, editAmount, editDescription, editTogle, _editAdvanceUpArrow, _editTextFieldUpArrow), editIncomeTabContent(setState, record), // 需同步修改Income tab的逻辑,参考Expense的修改方式 ], ), ), ], ), ), ); }, ); }, ); }
2. 更新editExpenseTabContent方法
Widget editExpenseTabContent( StateSetter setState, BudgetRecord record, TextEditingController editLabel, TextEditingController editAmount, TextEditingController editDescription, bool editTogle, bool _editAdvanceUpArrow, bool _editTextFieldUpArrow, ) { String formattedDate = CommonData.formatDateForEdit(record.date ?? record.createdAt); String formattedTime = CommonData.formatTimeForEdit( record.date ?? record.createdAt, record.time ?? record.createdAt); return Stack( children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 28.0, vertical: 65), child: Form( key: editExpenseForm, child: Stack( children: [ SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Container( height: 50, decoration: BoxDecoration( border: Border.all(color: Colors.grey), borderRadius: const BorderRadius.all(Radius.circular(30)), ), child: TextFormField( scrollPadding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom), focusNode: editLabeFocusNode, validator: (value) { if (value!.isEmpty) { return 'Field is required'; } return null; }, textInputAction: TextInputAction.done, controller: editLabel, onChanged: (value) {}, keyboardType: TextInputType.name, cursorColor: AppColors.mainColor, decoration: InputDecoration( suffixIcon: IconButton( onPressed: () { setState(() { _editTextFieldUpArrow = !_editTextFieldUpArrow; }); }, icon: _editTextFieldUpArrow ? Image.asset('assets/up_arrow.png') : Image.asset('assets/down_arrow.png')), labelStyle: const TextStyle(color: Colors.black), hintText: 'Label', contentPadding: const EdgeInsets.only(left: 20), border: const OutlineInputBorder( borderSide: BorderSide.none, )), ), ), const SizedBox(height: 10), Container( height: 50, decoration: BoxDecoration( border: Border.all(color: Colors.grey), borderRadius: const BorderRadius.all(Radius.circular(30)), ), child: TextFormField( scrollPadding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom), focusNode: editAmmountFocusNode, validator: (value) { if (value!.isEmpty) { return 'Field is required'; } return null; }, textInputAction: TextInputAction.done, controller: editAmount, keyboardType: const TextInputType.numberWithOptions(), inputFormatters: [FilteringTextInputFormatter.digitsOnly], cursorColor: AppColors.mainColor, decoration: const InputDecoration( labelStyle: TextStyle(color: Colors.black), hintText: 'Amount', contentPadding: EdgeInsets.only(left: 20), border: OutlineInputBorder( borderSide: BorderSide.none, )), ), ), const SizedBox(height: 10), Container( height: 50, decoration: BoxDecoration( border: Border.all(color: Colors.grey), borderRadius: const BorderRadius.all(Radius.circular(30)), ), child: TextFormField( scrollPadding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom), focusNode: editDescriptionFocusNode, controller: editDescription, keyboardType: TextInputType.name, cursorColor: AppColors.mainColor, decoration: const InputDecoration( labelStyle: TextStyle(color: Colors.black), hintText: 'Description', contentPadding: EdgeInsets.only(left: 20), border: OutlineInputBorder( borderSide: BorderSide.none, )), ), ), const SizedBox(height: 10), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ SizedBox( height: 35, child: TextButton.icon( onPressed: () { setState(() { _editAdvanceUpArrow = !_editAdvanceUpArrow; }); }, icon: _editAdvanceUpArrow ? Image.asset('assets/up_arrow.png') : Image.asset('assets/down_arrow.png'), label: const Text( 'Advance', style: TextStyle(color: AppColors.mainColor), )), ), Row( children: [ const Text('Paid ?'), Switch( activeColor: AppColors.mainColor, activeTrackColor: const Color.fromARGB(200, 124, 183, 191), inactiveThumbColor: AppColors.lightTextColor, inactiveTrackColor: Colors.grey.shade300, value: editTogle, onChanged: (value) { setState(() { editTogle = value; }); }), ], ), ], ), _editAdvanceUpArrow ? Column( children: [ GestureDetector( onTap: () => _selectDate(context, setState), child: AbsorbPointer( child: TextFormField( decoration: const InputDecoration(labelText: 'Date'), controller: TextEditingController(text: formattedDate), ), ), ), GestureDetector( onTap: () => _selectTime(context, setState), child: AbsorbPointer( child: TextFormField( decoration: const InputDecoration(labelText: 'Time'), controller: TextEditingController(text: formattedTime), ), ), ), ], ) : Container(), SizedBox(height: _editAdvanceUpArrow ? 20 : 15), ElevatedButton( onPressed: () { final recordd = BudgetRecord( uuid: record.uuid, type: 'Expense', label: editLabel.text, description: editDescription.text, amount: editAmount.text, done: editTogle, createdAt: DateTime.now().toUtc().toIso8601String(), addedBy: UserData().id, device: Platform.isAndroid ? 'Android' : "IOS", date: date.isEmpty ? null : date, time: time.isEmpty ? null : time, updatedAt: DateTime.now().toUtc().toIso8601String(), deletedAt: ""); addEditIRecord(recordd, true); CommonData.saveDataToMonthlyBudgetPrefrences(UserData().budget, UserData().email); setState(() { _editAdvanceUpArrow = false; Navigator.of(context).pop(); }); CommonData.showCustomSnackbar(context, 'Record updated Successfully'); }, style: ElevatedButton.styleFrom( elevation: 5, backgroundColor: AppColors.mainColor, minimumSize: const Size.fromHeight(55), ), child: const Text( "Update", style: TextStyle(color: AppColors.tabsColor), ), ), ], ), ), ], ), ), ), if (_editTextFieldUpArrow) Padding( padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 60), child: Padding( padding: const EdgeInsets.only(top: 60), child: Container( height: 250, decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(20)), ), child: ListView.builder( itemCount: expenseLabelList.length, itemBuilder: (context, index) { final name = expenseLabelList[index]; return Align( alignment: Alignment.topLeft, child: GestureDetector( onTap: () { setState(() { editLabel.text = name; _editTextFieldUpArrow = false; }); }, child: ListTile( leading: Text( name, style: const TextStyle(color: AppColors.darkTextColor, fontSize: 15), )), ), ); }, ), ), ), ) ], ); }
额外注意事项
- 移除原来的全局变量
editLabel、editAmount、editDescription,避免不同弹窗之间的状态污染。 - 同步修改
editIncomeTabContent方法的逻辑,确保Income Tab也不会出现同样的问题。
内容的提问来源于stack exchange,提问作者RIZWAN ALI
相关产品推荐
相关产品推荐

