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

Flutter滚动短列表并关闭键盘时出现Build scheduled during frame错误

错误根源分析

这个「Build scheduled during frame」错误的核心原因是:键盘收起触发页面布局变化时,AnimatedList内部的动画回调正好在当前构建/布局帧中触发了setState,加上快速重复触摸导致的频繁状态变更,最终引发帧内构建冲突。原代码存在两个关键问题:

  1. 每次触摸抬起都无条件调用unfocus,频繁触发不必要的布局变更;
  2. AnimatedList的itemBuilder未使用传入的animation参数,导致内部动画状态变化直接触发组件build,而非通过动画帧处理。

彻底解决的方案步骤

1. 用FocusNode精准控制文本焦点

替换全局FocusManager的方式,通过FocusNode直接绑定TextFormField,避免无效的焦点操作:

late FocusNode _textFocusNode;

@override
void initState() {
  super.initState();
  _scrollController = ScrollController(initialScrollOffset: 0);
  _textFocusNode = FocusNode();
}

@override
void dispose() {
  _scrollController.dispose();
  _textFocusNode.dispose();
  super.dispose();
}

修改TextFormField绑定焦点:

TextFormField(
  focusNode: _textFocusNode,
),

2. 优化unfocus的触发时机

仅在焦点存在时执行unfocus,并且用addPostFrameCallback确保操作在当前帧结束后执行,避开布局/构建帧:

onPointerUp: (_) {
  if (_textFocusNode.hasFocus) {
    SchedulerBinding.instance.addPostFrameCallback((_) {
      _textFocusNode.unfocus();
    });
  }
},

3. 正确使用AnimatedList的动画参数

将列表项用animation包裹,让动画变化在动画帧中处理,而非直接触发组件build:

itemBuilder: (context, index, animation) {
  return FadeTransition(
    opacity: animation,
    child: _buildItemWidget(_items[index]),
  );
},

如果不需要淡入动画,也可以用SizeTransition或AnimatedBuilder,核心是让动画状态变化通过动画系统处理,而非直接触发组件重建。

4. 实现滚动状态的动态控制

根据焦点状态设置AnimatedList的滚动物理特性,满足「键盘打开时可滚动,关闭时不可滚动」的需求:

AnimatedList(
  // 其他参数保持不变
  physics: _textFocusNode.hasFocus 
      ? const AlwaysScrollableScrollPhysics() 
      : const NeverScrollableScrollPhysics(),
),

完整修改后的代码

class _MyHomePageState extends State<MyHomePage> {
  static const _durationMin = Duration.zero;
  final List<String> _items = List.generate(7, (index) => "Item at $index");
  final GlobalKey<AnimatedListState> _listKey = GlobalKey();
  late ScrollController _scrollController;
  late FocusNode _textFocusNode;
  
  @override
  void initState() {
    super.initState();
    _scrollController = ScrollController(initialScrollOffset: 0);
    _textFocusNode = FocusNode();
  }

  @override
  void dispose() {
    _scrollController.dispose();
    _textFocusNode.dispose();
    super.dispose();
  }

  Widget _buildItemWidget(String text) {
    return ListTile(
      title: Text(text),
      trailing: IconButton(
        icon: const Icon(Icons.remove),
        onPressed: () {},
      ),
    );
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: SafeArea(
        child: Column(
          children: [
            TextFormField(
              focusNode: _textFocusNode,
            ),
            Expanded(
              child: Listener(
                onPointerUp: (_) {
                  if (_textFocusNode.hasFocus) {
                    SchedulerBinding.instance.addPostFrameCallback((_) {
                      _textFocusNode.unfocus();
                    });
                  }
                },
                child: AnimatedList(
                  key: _listKey,
                  padding: EdgeInsets.zero,
                  controller: _scrollController,
                  initialItemCount: _items.length,
                  physics: _textFocusNode.hasFocus 
                      ? const AlwaysScrollableScrollPhysics() 
                      : const NeverScrollableScrollPhysics(),
                  itemBuilder: (context, index, animation) {
                    return FadeTransition(
                      opacity: animation,
                      child: _buildItemWidget(_items[index]),
                    );
                  },
                ),
              ),
            ),
            Container(height: 100, color: Colors.red),
          ],
        ),
      ),
    );
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 09:07:28