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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 17:48:10