Flutter:BottomNavigationBar在选项卡更改时重建页面
Posted
技术标签:
【中文标题】Flutter:BottomNavigationBar在选项卡更改时重建页面【英文标题】:Flutter: BottomNavigationBar rebuilds Page on change of tab 【发布时间】:2019-03-07 00:33:18 【问题描述】:我在 Flutter 中的 BottomNavigationBar 有问题。如果我更改选项卡,我希望我的页面保持活跃。
这里是我的实现
底部导航
class Home extends StatefulWidget
@override
State<StatefulWidget> createState()
return _HomeState();
class _HomeState extends State<Home>
int _currentIndex = 0;
List<Widget> _children;
final Key keyOne = PageStorageKey("IndexTabWidget");
@override
void initState()
_children = [
IndexTabWidget(key: keyOne),
PlaceholderWidget(Colors.green),
NewsListWidget(),
ShopsTabWidget(),
PlaceholderWidget(Colors.blue),
];
super.initState();
@override
Widget build(BuildContext context)
return Scaffold(
appBar: AppBar(
title: Text(MyApp.appName),
textTheme: Theme.of(context).textTheme.apply(
bodyColor: Colors.black,
displayColor: Colors.blue,
),
),
body: _children[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
onTap: onTabTapped,
key: IHGApp.globalKey,
fixedColor: Colors.green,
type: BottomNavigationBarType.fixed,
currentIndex: _currentIndex,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Container(height: 0.0),
),
BottomNavigationBarItem(
icon: Icon(Icons.message),
title: Container(height: 0.0),
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
title: Container(height: 0.0),
),
BottomNavigationBarItem(
icon: Icon(Icons.perm_contact_calendar),
title: Container(height: 0.0),
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
title: Container(height: 0.0),
),
],
),
);
void onTabTapped(int index)
setState(()
_currentIndex = index;
);
Column buildButtonColumn(IconData icon)
Color color = Theme.of(context).primaryColor;
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: color),
],
);
这是我的索引页(第一个标签):
class IndexTabWidget extends StatefulWidget
IndexTabWidget(Key key) : super(key: key);
@override
State<StatefulWidget> createState()
return new IndexTabState();
class IndexTabState extends State<IndexTabWidget>
with AutomaticKeepAliveClientMixin
List<News> news = List();
FirestoreNewsRepo newsFirestore = FirestoreNewsRepo();
@override
Widget build(BuildContext context)
return Material(
color: Colors.white,
child: new Container(
child: new SingleChildScrollView(
child: new ConstrainedBox(
constraints: new BoxConstraints(),
child: new Column(
children: <Widget>[
HeaderWidget(
CachedNetworkImageProvider(
'https://static1.fashionbeans.com/wp-content/uploads/2018/04/50-barbershop-top-savill.jpg',
),
"",
),
AboutUsWidget(),
Padding(
padding: const EdgeInsets.all(16.0),
child: SectionTitleWidget(title: StringStorage.salonsTitle),
),
StreamBuilder(
stream: newsFirestore.observeNews(),
builder: (context, snapshot)
if (!snapshot.hasData)
return CircularProgressIndicator();
else
news = snapshot.data;
return Column(
children: <Widget>[
ShopItemWidget(
AssetImage('assets/images/picture.png'),
news[0].title,
news[0],
),
ShopItemWidget(
AssetImage('assets/images/picture1.png'),
news[1].title,
news[1],
)
],
);
,
),
Padding(
padding: const EdgeInsets.only(
left: 16.0, right: 16.0, bottom: 16.0),
child: SectionTitleWidget(title: StringStorage.galleryTitle),
),
GalleryCategoryCarouselWidget(),
],
),
),
),
),
);
@override
bool get wantKeepAlive => true;
因此,如果我从我的索引选项卡切换到任何其他选项卡并返回到索引选项卡,则索引选项卡将始终重建。我调试了一下,发现tab switch上总是调用build函数。
你们能帮我解决这个问题吗?
非常感谢 阿尔博
【问题讨论】:
在标签更改时,页面将获得re-build
。我们不能跳过这一点。将网络调用和与 DB 相关的想法移动到状态,以便为每个选项卡更改计算它。
所以与网络调用相关的所有内容都应该放在 initState 中?
@DineshBalasubramanian 我发现在选项卡更改时也会重新调用 initState。所以这不能解决问题....
我遇到了这个包:pub.dev/packages/persistent_bottom_nav_bar。它保持状态,所以没有重建完成。它还可以在选项卡内处理适当的独立和持久导航,并且不会像 IndexedStack
那样在开头加载所有选项卡。
【参考方案1】:
我之前的答案都没有。
在切换选项卡时保持页面活动的解决方案是将页面包装在 IndexedStack 中。
class Tabbar extends StatefulWidget
Tabbar(this.screens);
static const Tag = "Tabbar";
final List<Widget> screens;
@override
State<StatefulWidget> createState()
return _TabbarState();
class _TabbarState extends State<Tabbar>
int _currentIndex = 0;
Widget currentScreen;
@override
Widget build(BuildContext context)
var _l10n = PackedLocalizations.of(context);
return Scaffold(
body: IndexedStack(
index: _currentIndex,
children: widget.screens,
),
bottomNavigationBar: BottomNavigationBar(
fixedColor: Colors.black,
type: BottomNavigationBarType.fixed,
onTap: onTabTapped,
currentIndex: _currentIndex,
items: [
BottomNavigationBarItem(
icon: new Icon(Icons.format_list_bulleted),
title: new Text(_l10n.tripsTitle),
),
BottomNavigationBarItem(
icon: new Icon(Icons.settings),
title: new Text(_l10n.settingsTitle),
)
],
),
);
void onTabTapped(int index)
setState(()
_currentIndex = index;
);
【讨论】:
@basedgod PackedLocalizations 在这个解决方案中指的是什么?我还没有找到它的任何文档。 @PJQuakJag 指的是本地化字符串。 @basedgod 您的解决方案运行良好,但问题是所有屏幕选项卡都在同时构建,即使选项卡未处于活动状态。所以我想只在标签处于活动状态后才保持标签构建一次,有什么建议吗? 好人拯救了我的一天! @Fatimaayaa 很抱歉回复晚了,我决定在 CupertinoTabScaffold 中使用 CupertinoTabBar for ios 和 PersistentTabView(pub.dev/packages/persistent_bottom_nav_bar) for android。原因是我想拥有最原生的感觉和外观,同时仍然坚持在 Android 中。这是示例:gist.github.com/bparol/2ebaf11af962115dc598294bf136cb8d【参考方案2】:您需要使用导航器包装每个根页面(按下底部导航项时看到的第一个页面)并将它们放入堆栈中。
class HomePage extends StatefulWidget
@override
_HomePageState createState() => _HomePageState();
class _HomePageState extends State<HomePage>
final int _pageCount = 2;
int _pageIndex = 0;
@override
Widget build(BuildContext context)
return Scaffold(
body: _body(),
bottomNavigationBar: _bottomNavigationBar(),
);
Widget _body()
return Stack(
children: List<Widget>.generate(_pageCount, (int index)
return IgnorePointer(
ignoring: index != _pageIndex,
child: Opacity(
opacity: _pageIndex == index ? 1.0 : 0.0,
child: Navigator(
onGenerateRoute: (RouteSettings settings)
return new MaterialPageRoute(
builder: (_) => _page(index),
settings: settings,
);
,
),
),
);
),
);
Widget _page(int index)
switch (index)
case 0:
return Page1();
case 1:
return Page2();
throw "Invalid index $index";
BottomNavigationBar _bottomNavigationBar()
final theme = Theme.of(context);
return new BottomNavigationBar(
fixedColor: theme.accentColor,
currentIndex: _pageIndex,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.list),
title: Text("Page 1"),
),
BottomNavigationBarItem(
icon: Icon(Icons.account_circle),
title: Text("Page 2"),
),
],
onTap: (int index)
setState(()
_pageIndex = index;
);
,
);
页面将被重建,但无论如何您都应该将业务逻辑与 UI 分开。我更喜欢使用 BLoC 模式,但你也可以使用 Redux、ScopedModel 或 InhertedWidget。
【讨论】:
@fvisticot 我没有注意到性能有任何问题。 不幸的是,当使用此解决方案时,导航到另一条路线不再起作用,Navigator.of(context) .pushNamedAndRemoveUntil('/events', (Route只需使用 IndexedStack
IndexedStack(
index: selectedIndex,
children: <Widget> [
ProfileScreen(),
MapScreen(),
FriendsScreen()
],
)
【讨论】:
但是如果我在我的应用程序中使用 IndexedStack,那么当我想进入另一个屏幕时,我的 BottomMenu 会隐藏。任何解决方案。【参考方案4】:我不确定,但CupertinoTabBar
会有所帮助。
如果你不想要,这个视频会很棒url。
import 'dart:async';
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart';
class HomeScreen extends StatefulWidget
@override
_HomeScreenState createState() => new _HomeScreenState();
class _HomeScreenState extends State<HomeScreen>
final List<dynamic> pages = [
new Page1(),
new Page2(),
new Page3(),
new Page4(),
];
int currentIndex = 0;
@override
Widget build(BuildContext context)
return new WillPopScope(
onWillPop: () async
await Future<bool>.value(true);
,
child: new CupertinoTabScaffold(
tabBar: new CupertinoTabBar(
iconSize: 35.0,
onTap: (index)
setState(() => currentIndex = index);
,
activeColor: currentIndex == 0 ? Colors.white : Colors.black,
inactiveColor: currentIndex == 0 ? Colors.green : Colors.grey,
backgroundColor: currentIndex == 0 ? Colors.black : Colors.white,
currentIndex: currentIndex,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.looks_one),
title: Text(''),
),
BottomNavigationBarItem(
icon: Icon(Icons.looks_two),
title: Text(''),
),
BottomNavigationBarItem(
icon: Icon(Icons.looks_3),
title: Text(''),
),
BottomNavigationBarItem(
icon: Icon(Icons.looks_4),
title: Text(''),
),
],
),
tabBuilder: (BuildContext context, int index)
return new DefaultTextStyle(
style: const TextStyle(
fontFamily: '.SF UI Text',
fontSize: 17.0,
color: CupertinoColors.black,
),
child: new CupertinoTabView(
routes: <String, WidgetBuilder>
'/Page1': (BuildContext context) => new Page1(),
'/Page2': (BuildContext context) => new Page2(),
'/Page3': (BuildContext context) => new Page3(),
'/Page4': (BuildContext context) => new Page4(),
,
builder: (BuildContext context)
return pages[currentIndex];
,
),
);
,
),
);
class Page1 extends StatefulWidget
@override
_Page1State createState() => _Page1State();
class _Page1State extends State<Page1>
String title;
@override
void initState()
title = 'Page1';
super.initState();
@override
Widget build(BuildContext context)
return new Scaffold(
appBar: new AppBar(
title: new Text(title),
leading: new IconButton(
icon: new Icon(Icons.text_fields),
onPressed: ()
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => Page13()));
,
)),
body: new Center(
child: new Text(title),
),
);
class Page2 extends StatelessWidget
@override
Widget build(BuildContext context)
return new Scaffold(
appBar: new AppBar(
title: new Text('Page2'),
leading: new IconButton(
icon: new Icon(Icons.airline_seat_flat_angled),
onPressed: ()
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => Page12()));
,
)),
body: new Center(
child: Column(
children: <Widget>[
CupertinoSlider(
value: 25.0,
min: 0.0,
max: 100.0,
onChanged: (double value)
print(value);
),
],
),
),
);
class Page3 extends StatelessWidget
@override
Widget build(BuildContext context)
return new Scaffold(
appBar: new AppBar(
title: new Text('Page3'),
),
body: new Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new RaisedButton(
child: new Text('Cupertino'),
textColor: Colors.white,
color: Colors.red,
onPressed: ()
List<int> list = List.generate(10, (int i) => i + 1);
list.shuffle();
var subList = (list.sublist(0, 5));
print(subList);
subList.forEach((li) => list.remove(li));
print(list);
),
new SizedBox(height: 30.0),
new RaisedButton(
child: new Text('Android'),
textColor: Colors.white,
color: Colors.lightBlue,
onPressed: ()
var mes = 'message';
var messa = 'メッセージ';
var input = 'You have a new message';
if (input.contains(messa) || input.contains(mes))
print('object');
else
print('none');
),
],
),
),
);
class Page4 extends StatelessWidget
static List<int> ints = [1, 2, 3, 4, 5];
static _abc()
print(ints.last);
@override
Widget build(BuildContext context)
return new Scaffold(
appBar: new AppBar(
title: new Text('Page4'),
),
body: new Center(
child: new RaisedButton(
child: new Text('Static', style: new TextStyle(color: Colors.white)),
color: Colors.lightBlue,
onPressed: _abc,
)),
);
class Page12 extends StatelessWidget
@override
Widget build(BuildContext context)
return new Scaffold(
appBar: new AppBar(
title: new Text('Page12'),
actions: <Widget>[
new FlatButton(
child: new Text('GO'),
onPressed: ()
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => Page13()));
,
)
],
),
body: new Center(
child: new RaisedButton(
child: new Text('Swiper', style: new TextStyle(color: Colors.white)),
color: Colors.redAccent,
onPressed: () ,
)),
);
class Page13 extends StatefulWidget
@override
_Page13State createState() => _Page13State();
class _Page13State extends State<Page13> with SingleTickerProviderStateMixin
final List<String> _productLists = Platform.isAndroid
? [
'android.test.purchased',
'point_1000',
'5000_point',
'android.test.canceled',
]
: ['com.cooni.point1000', 'com.cooni.point5000'];
String _platformVersion = 'Unknown';
List<IAPItem> _items = [];
List<PurchasedItem> _purchases = [];
@override
void initState()
super.initState();
initPlatformState();
Future<void> initPlatformState() async
String platformVersion;
try
platformVersion = await FlutterInappPurchase.platformVersion;
on PlatformException
platformVersion = 'Failed to get platform version.';
var result = await FlutterInappPurchase.initConnection;
print('result: $result');
if (!mounted) return;
setState(()
_platformVersion = platformVersion;
);
// refresh items for android
String msg = await FlutterInappPurchase.consumeAllItems;
print('consumeAllItems: $msg');
Future<Null> _buyProduct(IAPItem item) async
try
PurchasedItem purchased = await FlutterInappPurchase.buyProduct(item.productId);
print('purchased: $purchased.toString()');
catch (error)
print('$error');
Future<Null> _getProduct() async
List<IAPItem> items = await FlutterInappPurchase.getProducts(_productLists);
print(items);
for (var item in items)
print('$item.toString()');
this._items.add(item);
setState(()
this._items = items;
this._purchases = [];
);
Future<Null> _getPurchases() async
List<PurchasedItem> items = await FlutterInappPurchase.getAvailablePurchases();
for (var item in items)
print('$item.toString()');
this._purchases.add(item);
setState(()
this._items = [];
this._purchases = items;
);
Future<Null> _getPurchaseHistory() async
List<PurchasedItem> items = await FlutterInappPurchase.getPurchaseHistory();
for (var item in items)
print('$item.toString()');
this._purchases.add(item);
setState(()
this._items = [];
this._purchases = items;
);
List<Widget> _renderInApps()
List<Widget> widgets = this
._items
.map((item) => Container(
margin: EdgeInsets.symmetric(vertical: 10.0),
child: Container(
child: Column(
children: <Widget>[
Container(
margin: EdgeInsets.only(bottom: 5.0),
child: Text(
item.toString(),
style: TextStyle(
fontSize: 18.0,
color: Colors.black,
),
),
),
FlatButton(
color: Colors.orange,
onPressed: ()
print("---------- Buy Item Button Pressed");
this._buyProduct(item);
,
child: Row(
children: <Widget>[
Expanded(
child: Container(
height: 48.0,
alignment: Alignment(-1.0, 0.0),
child: Text('Buy Item'),
),
),
],
),
),
],
),
),
))
.toList();
return widgets;
List<Widget> _renderPurchases()
List<Widget> widgets = this
._purchases
.map((item) => Container(
margin: EdgeInsets.symmetric(vertical: 10.0),
child: Container(
child: Column(
children: <Widget>[
Container(
margin: EdgeInsets.only(bottom: 5.0),
child: Text(
item.toString(),
style: TextStyle(
fontSize: 18.0,
color: Colors.black,
),
),
)
],
),
),
))
.toList();
return widgets;
@override
Widget build(BuildContext context)
double screenWidth = MediaQuery.of(context).size.width-20;
double buttonWidth=(screenWidth/3)-20;
return new Scaffold(
appBar: new AppBar(),
body: Container(
padding: EdgeInsets.all(10.0),
child: ListView(
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
child: Text(
'Running on: $_platformVersion\n',
style: TextStyle(fontSize: 18.0),
),
),
Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Container(
width: buttonWidth,
height: 60.0,
margin: EdgeInsets.all(7.0),
child: FlatButton(
color: Colors.amber,
padding: EdgeInsets.all(0.0),
onPressed: () async
print("---------- Connect Billing Button Pressed");
await FlutterInappPurchase.initConnection;
,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
alignment: Alignment(0.0, 0.0),
child: Text(
'Connect Billing',
style: TextStyle(
fontSize: 16.0,
),
),
),
),
),
Container(
width: buttonWidth,
height: 60.0,
margin: EdgeInsets.all(7.0),
child: FlatButton(
color: Colors.amber,
padding: EdgeInsets.all(0.0),
onPressed: () async
print("---------- End Connection Button Pressed");
await FlutterInappPurchase.endConnection;
setState(()
this._items = [];
this._purchases = [];
);
,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
alignment: Alignment(0.0, 0.0),
child: Text(
'End Connection',
style: TextStyle(
fontSize: 16.0,
),
),
),
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Container(
width: buttonWidth,
height: 60.0,
margin: EdgeInsets.all(7.0),
child: FlatButton(
color: Colors.green,
padding: EdgeInsets.all(0.0),
onPressed: ()
print("---------- Get Items Button Pressed");
this._getProduct();
,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
alignment: Alignment(0.0, 0.0),
child: Text(
'Get Items',
style: TextStyle(
fontSize: 16.0,
),
),
),
)),
Container(
width: buttonWidth,
height: 60.0,
margin: EdgeInsets.all(7.0),
child: FlatButton(
color: Colors.green,
padding: EdgeInsets.all(0.0),
onPressed: ()
print(
"---------- Get Purchases Button Pressed");
this._getPurchases();
,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
alignment: Alignment(0.0, 0.0),
child: Text(
'Get Purchases',
style: TextStyle(
fontSize: 16.0,
),
),
),
)),
Container(
width: buttonWidth,
height: 60.0,
margin: EdgeInsets.all(7.0),
child: FlatButton(
color: Colors.green,
padding: EdgeInsets.all(0.0),
onPressed: ()
print(
"---------- Get Purchase History Button Pressed");
this._getPurchaseHistory();
,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
alignment: Alignment(0.0, 0.0),
child: Text(
'Get Purchase History',
style: TextStyle(
fontSize: 16.0,
),
),
),
)),
]),
],
),
Column(
children: this._renderInApps(),
),
Column(
children: this._renderPurchases(),
),
],
),
],
),
),
);
【讨论】:
感谢您的回答!我已按照您提到的视频的说明进行操作。如果您滚动和切换选项卡,这有助于保留页面的状态。问题仍然存在:页面/视图将在选项卡切换后重建。我没有用 CupertinoTabBar 对其进行测试,但老实说,我不认为问题出在 TabBar 上。你用 CupertinoTabBar 测试过吗?当您切换标签时,构建功能是否会重新启动? 我不确定它是否适用于您的用例,但我有 4 个选项卡,除了第二个选项卡(即索引 1)之外,我实现了 StreamBuilder。所以这三个选项卡每次都会重建,但第二个选项卡不会重建。在第二个选项卡中,基本上我从互联网上获取数据并显示在网格中。 能否请您在答案中分享您的代码,以便我可以想象您是如何处理这个问题的?这正是我所需要的。 我编辑了我的答案我不确定这是正确的方法。基本上我在这个项目的主要项目中测试我想要的所有东西。所以,这些只是演示应用程序,但状态仍然存在。如果您从第一个选项卡转到 page13,然后移动到选项卡 index3,然后返回到第一个选项卡,它应该看起来相同。 我用CupertinoTabBar
,和作者有同样的问题。【参考方案5】:
如果您只需要记住列表中的滚动位置,最好的选择是简单地使用 PageStoreKey
对象作为 key
属性:
@override
Widget build(BuildContext context)
return Container(
child: ListView.builder(
key: PageStorageKey<String>('some-list-key'),
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: items.length,
itemBuilder: (BuildContext context, int index)
return GestureDetector(
onTap: () => _onElementTapped(index),
child: makeCard(items[index])
);
,
),
);
根据https://docs.flutter.io/flutter/widgets/PageStorageKey-class.html,这应该适用于任何可滚动的小部件。
【讨论】:
只有在滚动完成时才有效。如果您在滚动停止之前滚动并更改屏幕,则不会保存滚动位置。【参考方案6】:使用 IndexedStack 小部件:
class Home extends StatefulWidget
const Home(Key? key) : super(key: key);
@override
_HomeState createState() => _HomeState();
class _HomeState extends State<Home>
int _currentIndex = 0;
@override
Widget build(BuildContext context)
return Scaffold(
body: SafeArea(
child: IndexedStack(
index: _currentIndex,
children: const [
HomePage(),
SettingsPage(),
],
),
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (int index) => setState(() => _currentIndex = index),
items: [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.settings), label: 'Settings'),
],
),
);
class HomePage extends StatelessWidget
const HomePage(Key? key) : super(key: key);
@override
Widget build(BuildContext context)
print('build home');
return Center(child: Text('Home'));
class SettingsPage extends StatelessWidget
const SettingsPage(Key? key) : super(key: key);
@override
Widget build(BuildContext context)
print('build settings');
return Center(child: Text('Settings'));
确保 IndexedStack 子小部件列表保持不变。这将防止在调用 setState() 时重建小部件。
IndexedStack(
index: _currentIndex,
children: const [
HomeWidget(),
SettingsWidget(),
],
),
IndexedStack 的问题是所有的小部件都会在 IndexedStack 初始化的同时构建。对于小部件(如上面的示例),这不是问题。但对于大型小部件,您可能会看到一些性能问题。
考虑使用lazy_load_indexed_stack 包。根据包装:
[LazyLoadIndexedStack widget]只在需要的时候构建需要的widget,再次需要时返回预构建的widget
再次,确保 LazyLoadIndexedStack 子小部件是常量,否则在调用 setState 时它们将不断重建。
【讨论】:
【参考方案7】:使用带有 bloc 模式的 IndexedStack 解决了所有问题。
【讨论】:
您能否在回答中提供更多细节?以上是关于Flutter:BottomNavigationBar在选项卡更改时重建页面的主要内容,如果未能解决你的问题,请参考以下文章
Flutter 致命错误:找不到“Flutter/Flutter.h”文件
[Flutter] flutter项目一直卡在 Running Gradle task 'assembleDebug'...
flutter 日志输出,Flutter打印日志,flutter log,flutter 真机日志
Flutter开发 Flutter 包和插件 ( Flutter 包和插件简介 | 创建 Flutter 插件 | 创建 Dart 包 )