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

Flutter中ExpansionTile内TextField输入单字符后失焦问题求助

问题分析与解决

核心问题

你遇到的输入一个字符就失去焦点的问题,根源有两个:

  1. TextEditingController重复创建:在ListView.builder的itemBuilder内部每次都会新建TextEditingController,当你输入字符触发Firestore更新后,流数据变化会导致列表重新build,此时每个item都会重新创建controller,原有的输入状态和焦点被重置。
  2. 频繁触发重建:controller的addListener会在每输入一个字符就调用Firestore更新,这会导致流数据频繁刷新,进而触发列表频繁重建,加剧焦点丢失的问题。

解决步骤

1. 改用Map存储Controller(避免重复创建)

在有状态组件的initState中初始化一个Map,用来保存每个shoppingListId对应的TextEditingController,这样每次列表重建时,不会重新创建controller,而是复用已有的实例。

2. 添加防抖逻辑(减少Firestore更新频率)

直接在controller监听里调用Firestore更新会导致每输入一个字符就发起一次请求,既影响性能又会频繁触发组件重建。可以用debounce来延迟执行更新,比如等待用户停止输入300ms后再更新数据库。

3. 优化组件结构

确保你的组件是有状态组件,这样controller的状态能被正确保留,不会在重建时丢失。

修改后的代码示例

// 首先确保这是一个有状态组件
class YourShoppingListWidget extends StatefulWidget {
  // 你的widget参数
  final List<ShoppingListModel> shoppingListsCart;
  final String shoppingCartListId;
  final FirebaseCloudStorage firebaseCloudStorage;

  const YourShoppingListWidget({
    super.key,
    required this.shoppingListsCart,
    required this.shoppingCartListId,
    required this.firebaseCloudStorage,
  });

  @override
  State<YourShoppingListWidget> createState() => _YourShoppingListWidgetState();
}

class _YourShoppingListWidgetState extends State<YourShoppingListWidget> {
  // 用Map存储每个shoppingListId对应的controller
  final Map<String, TextEditingController> _controllers = {};
  // 防抖定时器
  Timer? _debounceTimer;

  @override
  void initState() {
    super.initState();
    // 初始化所有controller
    for (var list in widget.shoppingListsCart) {
      _controllers[list.shoppingListId] = TextEditingController(text: list.name);
      // 给每个controller添加监听
      _controllers[list.shoppingListId]?.addListener(() {
        _onListNameChanged(list.shoppingListId);
      });
    }
  }

  @override
  void didUpdateWidget(covariant YourShoppingListWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    // 当shoppingListsCart更新时,同步更新controller
    for (var list in widget.shoppingListsCart) {
      if (!_controllers.containsKey(list.shoppingListId)) {
        _controllers[list.shoppingListId] = TextEditingController(text: list.name);
        _controllers[list.shoppingListId]?.addListener(() {
          _onListNameChanged(list.shoppingListId);
        });
      } else if (_controllers[list.shoppingListId]?.text != list.name) {
        // 避免本地输入被数据库数据覆盖
        _controllers[list.shoppingListId]?.text = list.name;
      }
    }
    // 移除已删除列表的controller
    _controllers.removeWhere((key, value) => !widget.shoppingListsCart.any((list) => list.shoppingListId == key));
  }

  @override
  void dispose() {
    // 销毁所有controller
    for (var controller in _controllers.values) {
      controller.dispose();
    }
    _debounceTimer?.cancel();
    super.dispose();
  }

  void _onListNameChanged(String shoppingListId) {
    final controller = _controllers[shoppingListId];
    if (controller == null) return;
    
    // 防抖:取消之前的定时器,重新计时
    _debounceTimer?.cancel();
    _debounceTimer = Timer(const Duration(milliseconds: 300), () {
      widget.firebaseCloudStorage.updateShoppingListName(
        widget.shoppingCartListId,
        shoppingListId,
        controller.text,
      );
    });
  }

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: double.infinity,
      child: SingleChildScrollView(
        child: Column(
          children: [
            ListView.builder(
              physics: const ClampingScrollPhysics(),
              shrinkWrap: true,
              itemCount: widget.shoppingListsCart.length,
              itemBuilder: (BuildContext context, int index) {
                final shoppingList = widget.shoppingListsCart[index];
                final controller = _controllers[shoppingList.shoppingListId];
                
                return ExpansionTile(
                  backgroundColor: Colors.indigo,
                  title: SizedBox(
                    child: Row(
                      children: [
                        SizedBox(
                          width: 220,
                          child: TextField(
                            decoration: const InputDecoration(
                              border: InputBorder.none,
                              counterText: '',
                            ),
                            controller: controller,
                            maxLength: 35,
                          ),
                        ),
                        Row(
                          mainAxisAlignment: MainAxisAlignment.end,
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            IconButton(
                              onPressed: () {},
                              icon: const Icon(Icons.edit),
                            ),
                            IconButton(
                              onPressed: () {},
                              icon: const Icon(Icons.delete),
                            ),
                          ],
                        ),
                      ],
                    ),
                  ),
                  children: [
                    ProductsInShoppingListView(
                      shoppingListName: shoppingList.name,
                      shoppingCartId: widget.shoppingCartListId,
                      shoppingListId: shoppingList.shoppingListId,
                      firebaseCloudStorage: widget.firebaseCloudStorage,
                    ),
                  ],
                );
              },
            ),
            ProductsInCartView(
              shoppingCartId: widget.shoppingCartListId,
            ),
          ],
        ),
      ),
    );
  }
}

关键改动说明

  • Controller复用:用Map存储每个列表项的controller,在initState和didUpdateWidget中初始化和更新,避免每次build都新建。
  • 防抖逻辑:通过Timer延迟执行Firestore更新,减少请求频率和组件重建次数。
  • 生命周期管理:在dispose中销毁所有controller和定时器,避免内存泄漏。
  • 数据同步:在didUpdateWidget中处理列表数据更新时的controller同步,避免本地输入被覆盖。

内容的提问来源于stack exchange,提问作者jorge

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 02:56:11