每次按下导航项时,底部导航栏都会重新加载所有小部件
Bottom navigation bar is reloading all the widgets each time I press a navigation item
下面的代码几乎是从官方flutter GitHub底部导航栏示例中复制粘贴的。问题是,在打开包含视图的小部件时,所有内容都已预加载。每次我按下底部栏元素时,我都会重新加载所有视图。换句话说,当我按下第一项时,日志显示其他项(第二项和第三项)正在进行网络呼叫。当我按下第二个时,日志显示即使是第一个也在进行网络呼叫。当我调试时,我发现每次我从底部栏 select 一个小部件时,包含它的小部件都会重建(调用方法构建)。这是正常行为吗?这是包含小部件的代码
class MainScreen extends StatefulWidget{
@override
State<StatefulWidget> createState()=> MainScreenState();
}
class MainScreenState extends State<MainScreen> with TickerProviderStateMixin{
GlobalKey<ScaffoldState> scaffoldState = new GlobalKey();
int _currentSelection = 0;
BottomNavigationBarType _navigationBarType = BottomNavigationBarType.fixed;
Li
st<NavigationIconView> _navigationIcons;
@override
void initState() {
super.initState();
_navigationIcons = <NavigationIconView>[
new NavigationIconView(
icon: const Icon(Icons.home),
title: 'Главная',
vsync: this
),
new NavigationIconView(
icon: const Icon(Icons.map),
title: 'Квесты',
vsync: this
),
new NavigationIconView(
icon: const Icon(Icons.dehaze),
title: 'Профиль',
vsync: this
)
];
for(NavigationIconView v in _navigationIcons)
v.controller.addListener(_rebuild);
_navigationIcons[_currentSelection].controller.value = 1.0;
}
@override
void dispose() {
for(NavigationIconView v in _navigationIcons)
v.controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final BottomNavigationBar botNavBar = new BottomNavigationBar(
items: _navigationIcons
.map((NavigationIconView navigationView) => navigationView.item)
.toList(),
currentIndex: _currentSelection,
fixedColor: Colors.green,
type: _navigationBarType,
onTap: (int index) {
setState(() {
_navigationIcons[_currentSelection].controller.reverse();
_currentSelection = index;
_navigationIcons[_currentSelection].controller.forward();
print('pressed : $_currentSelection');
});
},
);
return new Scaffold(
key: scaffoldState,
body: new Center(
key: new Key('Main view container'),
child: new FutureBuilder<Widget>(
future: _buildTransitionsStack(),
builder: (BuildContext context, AsyncSnapshot<Widget> snapshot){
if(!snapshot.hasError) return snapshot.data;
else{
print('sh3t happened in main : ${snapshot.error}');
}
}
),
),
bottomNavigationBar: botNavBar,
);
}
Future<Widget> _buildTransitionsStack() {
final List<FadeTransition> transitions = <FadeTransition>[];
return _showMain().then((mainWidget){
transitions.add(_navigationIcons[0]
.transition(_navigationBarType,mainWidget, context));
print('size ${transitions.length}');
}).then((_){
transitions.add(_navigationIcons[1].transition(_navigationBarType,
_showQuest(), context));
transitions.add(_navigationIcons[2].transition(_navigationBarType,
_showProfile(), context));
transitions.sort((FadeTransition a, FadeTransition b) {
final Animation<double> aAnimation = a.opacity;
final Animation<double> bAnimation = b.opacity;
final double aValue = aAnimation.value;
final double bValue = bAnimation.value;
return aValue.compareTo(bValue);
});
return new Stack(children: transitions);
});
}
Future<Widget> _showMain(){
return _getToken().then((token){
return new FeedView(token);
});
}
Widget _showQuest(){
// return DetailableListScreen(ViewModelType.QUEST);
return new QuestScreen();
}
Widget _showProfile(){
return new Text('profile');
// TODO
}
void _rebuild() {
setState(() {
});
}
Future<String> _getToken() async{
return await SharedPreferences.getInstance()
.then((SharedPreferences sp)=> sp.getString(TOKEN)
);
}
}}
我绝对推荐观看 @amrnt 视频。但是对于那些想要直接答案的人,您需要为底部栏部分的每个页面实例化一个 PageStorageKey,然后让每个页面通过构造函数接收其 PageStorageKey。
切换选项卡时保持页面活动的解决方案是将页面包装在 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;
});
}
}
更好的方法是使用 IndexedStack 而不是 PageStorage 或 AutomaticKeepAliveClientMixin。
class _MainActivityState extends State<MainActivity> {
int _selectedPage = 0;
List<Widget> pageList = List<Widget>();
@override
void initState() {
pageList.add(HomePage());
pageList.add(ChatPage());
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: IndexedStack(
index: _selectedPage,
children: pageList,
),
//Bottom Navigation Bar added
bottomNavigationBar: BottomNavigationBar(
.....
IndexedStack 小部件是 Stack 小部件的 sub-class
它显示提供的孩子列表中的单个 child。它的大小和最大的一样大child。它保持所有孩子的状态。
下面的代码几乎是从官方flutter GitHub底部导航栏示例中复制粘贴的。问题是,在打开包含视图的小部件时,所有内容都已预加载。每次我按下底部栏元素时,我都会重新加载所有视图。换句话说,当我按下第一项时,日志显示其他项(第二项和第三项)正在进行网络呼叫。当我按下第二个时,日志显示即使是第一个也在进行网络呼叫。当我调试时,我发现每次我从底部栏 select 一个小部件时,包含它的小部件都会重建(调用方法构建)。这是正常行为吗?这是包含小部件的代码
class MainScreen extends StatefulWidget{
@override
State<StatefulWidget> createState()=> MainScreenState();
}
class MainScreenState extends State<MainScreen> with TickerProviderStateMixin{
GlobalKey<ScaffoldState> scaffoldState = new GlobalKey();
int _currentSelection = 0;
BottomNavigationBarType _navigationBarType = BottomNavigationBarType.fixed;
Li
st<NavigationIconView> _navigationIcons;
@override
void initState() {
super.initState();
_navigationIcons = <NavigationIconView>[
new NavigationIconView(
icon: const Icon(Icons.home),
title: 'Главная',
vsync: this
),
new NavigationIconView(
icon: const Icon(Icons.map),
title: 'Квесты',
vsync: this
),
new NavigationIconView(
icon: const Icon(Icons.dehaze),
title: 'Профиль',
vsync: this
)
];
for(NavigationIconView v in _navigationIcons)
v.controller.addListener(_rebuild);
_navigationIcons[_currentSelection].controller.value = 1.0;
}
@override
void dispose() {
for(NavigationIconView v in _navigationIcons)
v.controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final BottomNavigationBar botNavBar = new BottomNavigationBar(
items: _navigationIcons
.map((NavigationIconView navigationView) => navigationView.item)
.toList(),
currentIndex: _currentSelection,
fixedColor: Colors.green,
type: _navigationBarType,
onTap: (int index) {
setState(() {
_navigationIcons[_currentSelection].controller.reverse();
_currentSelection = index;
_navigationIcons[_currentSelection].controller.forward();
print('pressed : $_currentSelection');
});
},
);
return new Scaffold(
key: scaffoldState,
body: new Center(
key: new Key('Main view container'),
child: new FutureBuilder<Widget>(
future: _buildTransitionsStack(),
builder: (BuildContext context, AsyncSnapshot<Widget> snapshot){
if(!snapshot.hasError) return snapshot.data;
else{
print('sh3t happened in main : ${snapshot.error}');
}
}
),
),
bottomNavigationBar: botNavBar,
);
}
Future<Widget> _buildTransitionsStack() {
final List<FadeTransition> transitions = <FadeTransition>[];
return _showMain().then((mainWidget){
transitions.add(_navigationIcons[0]
.transition(_navigationBarType,mainWidget, context));
print('size ${transitions.length}');
}).then((_){
transitions.add(_navigationIcons[1].transition(_navigationBarType,
_showQuest(), context));
transitions.add(_navigationIcons[2].transition(_navigationBarType,
_showProfile(), context));
transitions.sort((FadeTransition a, FadeTransition b) {
final Animation<double> aAnimation = a.opacity;
final Animation<double> bAnimation = b.opacity;
final double aValue = aAnimation.value;
final double bValue = bAnimation.value;
return aValue.compareTo(bValue);
});
return new Stack(children: transitions);
});
}
Future<Widget> _showMain(){
return _getToken().then((token){
return new FeedView(token);
});
}
Widget _showQuest(){
// return DetailableListScreen(ViewModelType.QUEST);
return new QuestScreen();
}
Widget _showProfile(){
return new Text('profile');
// TODO
}
void _rebuild() {
setState(() {
});
}
Future<String> _getToken() async{
return await SharedPreferences.getInstance()
.then((SharedPreferences sp)=> sp.getString(TOKEN)
);
}
}}
我绝对推荐观看 @amrnt 视频。但是对于那些想要直接答案的人,您需要为底部栏部分的每个页面实例化一个 PageStorageKey,然后让每个页面通过构造函数接收其 PageStorageKey。
切换选项卡时保持页面活动的解决方案是将页面包装在 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;
});
}
}
更好的方法是使用 IndexedStack 而不是 PageStorage 或 AutomaticKeepAliveClientMixin。
class _MainActivityState extends State<MainActivity> {
int _selectedPage = 0;
List<Widget> pageList = List<Widget>();
@override
void initState() {
pageList.add(HomePage());
pageList.add(ChatPage());
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: IndexedStack(
index: _selectedPage,
children: pageList,
),
//Bottom Navigation Bar added
bottomNavigationBar: BottomNavigationBar(
.....
IndexedStack 小部件是 Stack 小部件的 sub-class 它显示提供的孩子列表中的单个 child。它的大小和最大的一样大child。它保持所有孩子的状态。