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

Flutter自定义组件不在视口时监听器未触发的原因及解决方法

问题描述

基于flutter_sticky_widgets包实现了自定义粘性计时器组件,计时器停止或重置时,通过TextEditingController更新屏幕下方的自定义组件。当两个组件都处于视口范围内时功能正常,但下方组件完全移出视口后,监听器不再收到通知。使用TextField替代自定义组件则无此问题,请问问题原因及解决方法是什么?

原因分析
  1. ListView组件回收机制:ListView会将完全移出视口的组件从Widget树中移除,并调用其dispose()方法。你的TimeEditWidget及子组件CustomInput在dispose()中移除了TextEditingController的监听器,导致后续控制器更新时无法触发回调。
  2. TextField的特殊处理:TextField内部对控制器的监听逻辑不受组件是否在视口内的影响,即使组件被回收,监听逻辑仍能正常工作,因此可以接收通知。
解决方法

方法一:用AutomaticKeepAliveClientMixin阻止组件回收

让TimeEditWidget和CustomInput的State混入AutomaticKeepAliveClientMixin,告知ListView即使组件移出视口也不要回收:

class _TimeEditWidgetState extends State<TimeEditWidget> with AutomaticKeepAliveClientMixin {
  // 原有代码不变

  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context); // 必须调用super.build
    // 原有build代码不变
  }
}

同样给CustomInput的State添加该Mixin:

class _CustomInputState extends State<CustomInput> with AutomaticKeepAliveClientMixin {
  // 原有代码不变

  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context);
    // 原有build代码不变
  }
}

方法二:将监听器移至父组件

把TimeEditWidget对_time控制器的监听逻辑移到MyHomePage的State中,即使子组件被回收,父组件的监听依然生效,再通过参数传递更新子组件状态:

修改MyHomePage的State:

class _MyHomePageState extends State<MyHomePage> {
  final _controller = ScrollController();
  final _time = TextEditingController();
  String currentTime = "00:00.000";

  @override
  void initState() {
    super.initState();
    _time.addListener(() {
      setState(() {
        currentTime = _time.text;
      });
    });
  }

  @override
  void dispose() {
    _time.removeListener(() {});
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 原有代码不变
      body: StickyContainer(
        // 原有代码不变
        child: ListView(controller: _controller, children: [
          ...getFifty(),
          TimeEditWidget(
            currentTime: currentTime,
            timerNum: 1,
            onChanged: (value) {
              print('changed');
              _time.text = value;
            },
          ),
        ]),
      ),
    );
  }
}

修改TimeEditWidget,通过父组件传递的currentTime更新内部状态:

class TimeEditWidget extends StatefulWidget {
  const TimeEditWidget({
    required this.currentTime,
    this.timerNum,
    this.onChanged,
    super.key,
  });
  final String currentTime;
  final int? timerNum;
  final void Function(String value)? onChanged;

  @override
  State<TimeEditWidget> createState() => _TimeEditWidgetState();
}

class _TimeEditWidgetState extends State<TimeEditWidget> {
  final minuteController = TextEditingController();
  final secondController = TextEditingController();
  final millisecondController = TextEditingController();
  String mins = "00";
  String secs = "00";
  String ms = "000";

  @override
  void didUpdateWidget(covariant TimeEditWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.currentTime != widget.currentTime) {
      setTime(widget.currentTime);
    }
  }

  void setTime(String time) {
    mins = time.split(':')[0];
    secs = time.split(':')[1].split('.')[0];
    ms = time.split('.')[1];
    minuteController.text = mins;
    secondController.text = secs;
    millisecondController.text = ms;
    if (widget.onChanged != null) {
      widget.onChanged!('$mins:$secs.$ms');
    }
  }

  @override
  void initState() {
    super.initState();
    setTime(widget.currentTime);
  }

  // 移除原有控制器监听相关代码,保留其他逻辑
}

方法三:用ValueNotifier替代TextEditingController

TextEditingController主要用于TextField交互,若无需绑定输入框,可改用ValueNotifier管理时间状态,其监听不受组件回收影响:

修改MyHomePage的State:

class _MyHomePageState extends State<MyHomePage> {
  final _controller = ScrollController();
  final _timeNotifier = ValueNotifier<String>("00:00.000");

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 原有代码不变
      body: StickyContainer(
        stickyChildren: [
          StickyWidget(
            // 原有代码不变
            child: Container(
              // 原有代码不变
              child: Flex(
                // 原有代码不变
                children: [
                  Flexible(
                    child: TimerWidget(
                      timerNum: 1,
                      onTimeSet: (value) {
                        _timeNotifier.value = value;
                      },
                    ),
                  ),
                ],
              ),
            ),
          ),
        ],
        child: ListView(controller: _controller, children: [
          ...getFifty(),
          ValueListenableBuilder(
            valueListenable: _timeNotifier,
            builder: (context, value, child) {
              return TimeEditWidget(
                currentTime: value,
                timerNum: 1,
                onChanged: (newValue) {
                  _timeNotifier.value = newValue;
                },
              );
            },
          ),
        ]),
      ),
    );
  }

  @override
  void dispose() {
    _timeNotifier.dispose();
    super.dispose();
  }
}

TimeEditWidget的修改逻辑同方法二,不再依赖TextEditingController,通过currentTime参数更新状态。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 02:25:55