Flutter自定义组件不在视口时监听器未触发的原因及解决方法
问题描述
基于flutter_sticky_widgets包实现了自定义粘性计时器组件,计时器停止或重置时,通过TextEditingController更新屏幕下方的自定义组件。当两个组件都处于视口范围内时功能正常,但下方组件完全移出视口后,监听器不再收到通知。使用TextField替代自定义组件则无此问题,请问问题原因及解决方法是什么?
原因分析
- ListView组件回收机制:ListView会将完全移出视口的组件从Widget树中移除,并调用其
dispose()方法。你的TimeEditWidget及子组件CustomInput在dispose()中移除了TextEditingController的监听器,导致后续控制器更新时无法触发回调。 - 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
相关产品推荐
相关产品推荐

