AutomaticKeepAliveClientMixin与CupertinoSwitch搭配时状态无法保持问题
问题描述
我开发了一个通过ListView构建包含CupertinoSwitch组件的页面,已为NewsPageRegistr、CheckWidget及PageView主组件添加AutomaticKeepAliveClientMixin并设置wantKeepAlive为true,但在PageView中切换页面时,NewsPageRegistr的状态仍会被销毁。
相关代码
NewsPageRegistr 代码
class NewsPageRegistr extends StatefulWidget { NewsPageRegistr({Key key, this.model}) : super(key: key); RegistrationModel model; @override _NewsPageRegistrState createState() => _NewsPageRegistrState(); } class _NewsPageRegistrState extends State<NewsPageRegistr> with AutomaticKeepAliveClientMixin<NewsPageRegistr> { @override bool get wantKeepAlive => true; var responseV; List<DigitalNews> digitalNews = []; @override void dispose() { super.dispose(); } @override void initState() { fitchData(); super.initState(); } fitchData() async { responseV = await apiRegistrationDataList(DropMenuMode.news); for (var values in responseV) { digitalNews.add(DigitalNews.fromJson(values)); } } @override Widget build(BuildContext context) { super.build(context); return SingleChildScrollView( child: SafeArea( minimum: const EdgeInsets.fromLTRB(25, 0, 25, 0), child: Column( mainAxisSize: MainAxisSize.max, children: <Widget>[ SizedBox( width: 400, height: 85, child: Text( "Ich möchte folgende Newsletter abonnieren", textAlign: TextAlign.left, style: GeneralStyles.titleStyle, ), ), ListView.builder( //padding: const EdgeInsets.symmetric(vertical: 8), physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, itemCount: digitalNews.length, itemBuilder: (context, index) { return CheckWidget( title: digitalNews[index].displayName, subtitle: digitalNews[index].displayDescription, registrationName: digitalNews[index].name, index: index, model: widget.model, ); }), Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ Container( child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ OutlinedButton( style: OutlinedButton.styleFrom( backgroundColor: GeneralStyles.petrolColor, ), onPressed: () { setState(() {}); widget.model.setCurrentPage(2); }, child: const Text( "Weiter", style: TextStyle( color: Colors.white, fontWeight: FontWeight.w300, fontFamily: 'FiraSans'), )), ], ), ), ], ), ), ], ), ), ); } }
CheckWidget 代码
class CheckWidget extends StatefulWidget { RegistrationModel model; bool checker = false; final Function(String) onChanged; final String title; final String subtitle; final String registrationName; final int index; CheckWidget({Key key, this.onChanged, this.title, this.subtitle, this.registrationName, this.index,this.model}) : super(key: key); @override State<CheckWidget> createState() => _CheckWidgetState(); } class _CheckWidgetState extends State<CheckWidget> { @override Widget build(BuildContext context) { return Container( child: Row( mainAxisAlignment: MainAxisAlignment.start, children: <Widget>[ Transform.scale( alignment: Alignment.centerLeft, scale: 0.8, child: CupertinoSwitch( value: widget.checker, activeColor: GeneralStyles.petrolColor, onChanged: (bool value) { setState(() { widget.checker = value; if(value == true) { widget.model.newsPapers[widget.index] = widget.registrationName; }else { widget.model.newsPapers[widget.index] = ''; } }); }, ), ), Expanded( child: GestureDetector( child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(widget.title.replaceFirst("Newsletter", "").trim(), style:TextStyle( color: GeneralStyles.blackColor, fontSize: 15, fontFamily: 'FiraSans', fontWeight: FontWeight.w400, )), Text(widget.subtitle, style:TextStyle( color: GeneralStyles.blackColor, fontSize: 13, fontFamily: 'FiraSans', fontWeight: FontWeight.w300, )), ], ), onTap: (){ setState(() { if(widget.checker == false) { widget.checker = true; widget.model.newsPapers[widget.index] = widget.registrationName; }else { widget.checker = false; widget.model.newsPapers[widget.index] = ''; } }); }, ), ), ] ), ); } }
解决方案
1. 修正CheckWidget的状态存储逻辑
StatefulWidget的成员变量属于Widget实例,是不可变的,直接修改会导致重建时状态丢失。需要将checker移到_CheckWidgetState中存储,并从RegistrationModel同步初始状态:
class CheckWidget extends StatefulWidget { final RegistrationModel model; final Function(String) onChanged; final String title; final String subtitle; final String registrationName; final int index; CheckWidget({Key key, this.onChanged, this.title, this.subtitle, this.registrationName, this.index,this.model}) : super(key: key); @override State<CheckWidget> createState() => _CheckWidgetState(); } class _CheckWidgetState extends State<CheckWidget> with AutomaticKeepAliveClientMixin<CheckWidget> { bool checker = false; @override bool get wantKeepAlive => true; @override void initState() { super.initState(); // 从model中同步初始选中状态 checker = widget.model.newsPapers[widget.index] != ''; } @override Widget build(BuildContext context) { super.build(context); return Container( child: Row( mainAxisAlignment: MainAxisAlignment.start, children: <Widget>[ Transform.scale( alignment: Alignment.centerLeft, scale: 0.8, child: CupertinoSwitch( value: checker, activeColor: GeneralStyles.petrolColor, onChanged: (bool value) { setState(() { checker = value; widget.model.newsPapers[widget.index] = value ? widget.registrationName : ''; }); }, ), ), Expanded( child: GestureDetector( child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(widget.title.replaceFirst("Newsletter", "").trim(), style:TextStyle( color: GeneralStyles.blackColor, fontSize: 15, fontFamily: 'FiraSans', fontWeight: FontWeight.w400, )), Text(widget.subtitle, style:TextStyle( color: GeneralStyles.blackColor, fontSize: 13, fontFamily: 'FiraSans', fontWeight: FontWeight.w300, )), ], ), onTap: (){ setState(() { checker = !checker; widget.model.newsPapers[widget.index] = checker ? widget.registrationName : ''; }); }, ), ), ] ), ); } }
2. 扩大PageView的缓存范围
PageView默认仅缓存当前页面前后各1个页面,若切换跨度超过此范围,需设置cacheExtent参数扩大缓存:
PageView( cacheExtent: 3, // 根据实际页面数量调整,比如缓存3个页面 children: [/* 你的页面列表 */], // 其他配置 )
3. 修复NewsPageRegistr的数据加载更新
在fitchData中获取数据后需调用setState更新UI,确保数据变化能触发ListView重建:
fitchData() async { responseV = await apiRegistrationDataList(DropMenuMode.news); setState(() { digitalNews.clear(); for (var values in responseV) { digitalNews.add(DigitalNews.fromJson(values)); } }); }
问题核心原因
- 错误地将状态存储在StatefulWidget实例而非State类中,导致Widget重建时状态丢失;
- PageView默认缓存范围有限,切换页面超出范围时会销毁页面状态;
- 数据加载后未调用
setState更新UI,页面重建时无法恢复数据。
内容的提问来源于stack exchange,提问作者Kouki
相关产品推荐
相关产品推荐

