Flutter BottomNavigationBar复用组件时页面不切换问题排查
我在Flutter中实现了BottomNavigationBar,发现底部导航对应的两个页面组件仅两个参数不同、逻辑高度相似,于是将它们合并为一个WorkoutListPage,通过底部导航页面传递不同参数。但修改后切换底部导航时页面无法正常更新,通常仅显示初始tab(index=0)对应的内容;而把其中一个tab改为指向不同的FreestyleDDPage组件时,页面则能正常切换。
相关代码如下:
底部导航页面代码
class FreestylePage extends Stateful Widget { const FreestylePage({Key? key}) : super(key: key); @override State<StatefulWidget> createState() { return _FreestylePageState(); } } class _FreestylePageState extends State<FreestylePage> { int _currentIndex = 0; @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: showCategory(_currentIndex), )), bottomNavigationBar: BottomNavigationBar( currentIndex: _currentIndex, items: const [ BottomNavigationBarItem( icon: Icon(Icons.looks_one_outlined), label: 'Single rope', backgroundColor: Color.fromRGBO(204, 16, 138, 1)), BottomNavigationBarItem( icon: Icon(Icons.looks_two_outlined), label: 'Double dutch', backgroundColor: Color.fromRGBO(204, 16, 138, 1)), ], onTap: (index) { if (mounted) { setState(() { _currentIndex = index; }); } }, ), ); } showCategory(index) { if (index == 0) { return [ WorkoutListPage(categoryIndex: 2, subCategories: Utils.srfDropdown) ]; } else { return [ WorkoutListPage(categoryIndex: 3, subCategories: Utils.ddfDropdown) ]; } } }
WorkoutListPage代码
class WorkoutListPage extends Stateful Widget { final int categoryIndex; final List<String> subCategories; const WorkoutListPage( {Key? key, required this.categoryIndex, required this.subCategories}) : super(key: key); @override State<StatefulWidget> createState() { return _WorkoutListPageState(); } } class _WorkoutListPageState extends State<WorkoutListPage> { bool isLoading = true; @override void initState() { super.initState(); } @override Widget build(BuildContext context) => FutureBuilder<List<Map<String, dynamic>>>( future: MyCard.getData(widget.categoryIndex, widget.subCategories)! .whenComplete(() => setState(() { isLoading = false; })), builder: ((context, snapshot) { if (snapshot.hasData && snapshot.data!.isNotEmpty) { return FutureBuilder<List<MyCard>>( future: MyCard.readData(snapshot.data), builder: (context, cards) { if (cards.hasData) { final card = cards.data!; return Expanded( child: ListView.builder( padding: const EdgeInsets.all(16), itemCount: card.length, itemBuilder: (context, index) { return MyCard.buildCard(card[index], context); }, ), ); } else { return const Text("No data"); } }); } else { return isLoading ? Column( children: const [CircularProgressIndicator()], ) : const Text("You do not have any workouts yet"); } }), ); }
可正常工作的修改后showCategory函数
showCategory(index) { if (index == 0) { return [ WorkoutListPage(categoryIndex: 2, subCategories: Utils.srfDropdown) ]; } else { return [const FreestyleDDPage()]; } }
FreestyleDDPage代码
class FreestyleDDPage extends Stateful Widget { const FreestyleDDPage({Key? key}) : super(key: key); @override State<StatefulWidget> createState() { return _FreestyleDDPageState(); } } class _FreestyleDDPageState extends State<FreestyleDDPage> { var isLoading = true; @override Widget build(BuildContext context) => FutureBuilder<List<Map<String, dynamic>>>( future: MyCard.getData(3, Utils.ddfDropdown)! .whenComplete(() => setState(() { isLoading = false; })), builder: ((context, snapshot) { if (snapshot.hasData && snapshot.data!.isNotEmpty) { return FutureBuilder<List<MyCard>>( future: MyCard.readData(snapshot.data), builder: (context, cards) { if (cards.hasData) { final card = cards.data!; return Expanded( child: ListView.builder( padding: const EdgeInsets.all(16), itemCount: card.length, itemBuilder: (context, index) { return MyCard.buildCard(card[index], context); }, ), ); } else { return const Text("No data"); } }); } else { return isLoading ? Column( children: const [CircularProgressIndicator()], ) : const Text("You do not have any workouts yet"); } }), ); }
问题原因
Flutter通过Key识别Widget实例是否相同。切换tab时,两个WorkoutListPage属于同一类型且未指定唯一Key,Flutter会判定为同一个Widget,不会重新创建State;而FutureBuilder在initState阶段已执行初始参数的请求,后续参数变化无法触发新的数据请求,导致页面内容不更新。
解决方案
方案1:给每个WorkoutListPage添加唯一Key
让Flutter识别为不同的Widget实例,触发State重建:
showCategory(index) { if (index == 0) { return [ WorkoutListPage( key: const ValueKey(2), categoryIndex: 2, subCategories: Utils.srfDropdown ) ]; } else { return [ WorkoutListPage( key: const ValueKey(3), categoryIndex: 3, subCategories: Utils.ddfDropdown ) ]; } }
方案2:监听参数变化重新请求数据
在WorkoutListPage的State中重写didUpdateWidget方法,参数变化时重新发起请求:
class _WorkoutListPageState extends State<WorkoutListPage> { bool isLoading = true; late Future<List<Map<String, dynamic>>> _dataFuture; @override void initState() { super.initState(); _dataFuture = _fetchData(); } @override void didUpdateWidget(covariant WorkoutListPage oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.categoryIndex != widget.categoryIndex || oldWidget.subCategories != widget.subCategories) { setState(() { isLoading = true; _dataFuture = _fetchData(); }); } } Future<List<Map<String, dynamic>>> _fetchData() { return MyCard.getData(widget.categoryIndex, widget.subCategories)! .whenComplete(() => setState(() { isLoading = false; })); } @override Widget build(BuildContext context) => FutureBuilder<List<Map<String, dynamic>>>( future: _dataFuture, // 原有builder逻辑保持不变 ); }
两种方案均可解决问题,方案1简单直接,方案2适合需要保留State状态的场景。
内容的提问来源于stack exchange,提问作者FlutterBuilder
相关产品推荐
相关产品推荐

