当使用 BottomNavigationBar 导航时,如何保持小部件的颤动状态?

我目前正在构建一个 Flutter 应用程序,它可以在从一个屏幕导航到另一个屏幕时保持状态,在使用 底部导航栏时再次保持状态。就像 Spotify 移动应用程序一样,如果你已经在一个主屏幕的导航层次结构中导航到一定的级别,通过底部的导航栏改变屏幕,然后再变回原来的屏幕,将保留用户在该层次结构中的位置,包括保留状态。

我一直在想办法,尝试各种各样的事情,但都没有成功。

I want to know how I can prevent the pages in pageChooser(), when toggled once the user taps the BottomNavigationBar item, from rebuilding themselves, and instead preserve the state they already found themselves in (the pages are all stateful Widgets).

import 'package:flutter/material.dart';
import './page_plan.dart';
import './page_profile.dart';
import './page_startup_namer.dart';


void main() => runApp(new Recipher());


class Recipher extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Pages();
}
}


class Pages extends StatefulWidget {
@override
createState() => new PagesState();
}


class PagesState extends State<Pages> {
int pageIndex = 0;




pageChooser() {
switch (this.pageIndex) {
case 0:
return new ProfilePage();
break;


case 1:
return new PlanPage();
break;


case 2:
return new StartUpNamerPage();
break;


default:
return new Container(
child: new Center(
child: new Text(
'No page found by page chooser.',
style: new TextStyle(fontSize: 30.0)
)
),
);
}
}


@override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
body: pageChooser(),
bottomNavigationBar: new BottomNavigationBar(
currentIndex: pageIndex,
onTap: (int tappedIndex) { //Toggle pageChooser and rebuild state with the index that was tapped in bottom navbar
setState(
(){ this.pageIndex = tappedIndex; }
);
},
items: <BottomNavigationBarItem>[
new BottomNavigationBarItem(
title: new Text('Profile'),
icon: new Icon(Icons.account_box)
),
new BottomNavigationBarItem(
title: new Text('Plan'),
icon: new Icon(Icons.calendar_today)
),
new BottomNavigationBarItem(
title: new Text('Startup'),
icon: new Icon(Icons.alarm_on)
)
],
)
)
);
}
}
63944 次浏览

不要在每次运行 pageChooser时都返回新实例,而是创建一个实例并返回相同的实例。

例如:

class Pages extends StatefulWidget {
@override
createState() => new PagesState();
}


class PagesState extends State<Pages> {
int pageIndex = 0;


// Create all the pages once and return same instance when required
final ProfilePage _profilePage = new ProfilePage();
final PlanPage _planPage = new PlanPage();
final StartUpNamerPage _startUpNamerPage = new StartUpNamerPage();




Widget pageChooser() {
switch (this.pageIndex) {
case 0:
return _profilePage;
break;


case 1:
return _planPage;
break;


case 2:
return _startUpNamerPage;
break;


default:
return new Container(
child: new Center(
child: new Text(
'No page found by page chooser.',
style: new TextStyle(fontSize: 30.0)
)
),
);
}
}


@override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
body: pageChooser(),
bottomNavigationBar: new BottomNavigationBar(
currentIndex: pageIndex,
onTap: (int tappedIndex) { //Toggle pageChooser and rebuild state with the index that was tapped in bottom navbar
setState(
(){ this.pageIndex = tappedIndex; }
);
},
items: <BottomNavigationBarItem>[
new BottomNavigationBarItem(
title: new Text('Profile'),
icon: new Icon(Icons.account_box)
),
new BottomNavigationBarItem(
title: new Text('Plan'),
icon: new Icon(Icons.calendar_today)
),
new BottomNavigationBarItem(
title: new Text('Startup'),
icon: new Icon(Icons.alarm_on)
)
],
)
)
);
}
}

或者您可以使用像 PageViewStack这样的小部件来实现同样的目标。

Hope that helps!

使用 AutomaticKeepAliveClientMixin强制不释放选项卡内容。

class PersistantTab extends StatefulWidget {
@override
_PersistantTabState createState() => _PersistantTabState();
}


class _PersistantTabState extends State<PersistantTab> with AutomaticKeepAliveClientMixin {
@override
Widget build(BuildContext context) {
return Container();
}


// Setting to true will force the tab to never be disposed. This could be dangerous.
@override
bool get wantKeepAlive => true;
}

为了确保您的选项卡在不需要持久化时被释放,使 wantKeepAlive返回一个类变量。您必须调用 updateKeepAlive()来更新保持活动状态。

动态保持活力的例子:

// class PersistantTab extends StatefulWidget ...


class _PersistantTabState extends State<PersistantTab>
with AutomaticKeepAliveClientMixin {
bool keepAlive = false;


@override
void initState() {
doAsyncStuff();
}


Future doAsyncStuff() async {
keepAlive = true;
updateKeepAlive();
// Keeping alive...


await Future.delayed(Duration(seconds: 10));


keepAlive = false;
updateKeepAlive();
// Can be disposed whenever now.
}


@override
bool get wantKeepAlive => keepAlive;


@override
Widget build(BuildContext context) {
super.build();
return Container();
}
}

我找到的最方便的方法是使用 PageStorage 小部件和 PageStorageBucket,PageStorageBucket 充当键值持久层。

通过阅读这篇文章获得一个漂亮的解释-> https://steemit.com/utopian-io/@tensor/persisting-user-interface-state-and-building-bottom-navigation-bars-in-dart-s-flutter-framework

为了将状态保持在 BottomNavigationBar中,可以使用 IndexedStack

    @override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: BottomNavigationBar(
onTap: (index) {
setState(() {
current_tab = index;
});
},
currentIndex: current_tab,
items: [
BottomNavigationBarItem(
...
),
BottomNavigationBarItem(
...
),
],
),
body: IndexedStack(
children: <Widget>[
PageOne(),
PageTwo(),
],
index: current_tab,
),
);
}

使用“ IndexedStack 小部件”和“ Bottom Navigation Bar Widget”来保持屏幕/页面/小部件的状态

Provide list of Widget to IndexedStack and index of widget you want to show because IndexedStack show single widget from list at one time.

final List<Widget> _children = [
FirstClass(),
SecondClass()
];


Scaffold(
body: IndexedStack(
index: _selectedPage,
children: _children,
),
bottomNavigationBar: BottomNavigationBar(
........
........
),
);

虽然迟到了,但是我有一个简单的解决方案。 使用 PageView小部件和 AutomaticKeepAliveClinetMixin

它的美丽之处在于,它不会加载任何标签,直到你点击它。


包含 BottomNavigationBar的页面:

var _selectedPageIndex;
List<Widget> _pages;
PageController _pageController;


@override
void initState() {
super.initState();


_selectedPageIndex = 0;
_pages = [
//The individual tabs.
];


_pageController = PageController(initialPage: _selectedPageIndex);
}


@override
void dispose() {
_pageController.dispose();


super.dispose();
}


@override
Widget build(BuildContext context) {
...
body: PageView(
controller: _pageController,
physics: NeverScrollableScrollPhysics(),
children: _pages,
),
bottomNavigationBar: BottomNavigationBar(
...
currentIndex: _selectedPageIndex,
onTap: (selectedPageIndex) {
setState(() {
_selectedPageIndex = selectedPageIndex;
_pageController.jumpToPage(selectedPageIndex);
});
},
...
}

The individual tab:

class _HomeState extends State<Home> with AutomaticKeepAliveClientMixin<Home> {
@override
bool get wantKeepAlive => true;


@override
Widget build(BuildContext context) {
//Notice the super-call here.
super.build(context);
...
}
}

我做了一个关于它的视频。

不要使用 IndexStack Widget,因为它会一起实例化所有的选项卡,并且假设如果所有的选项卡都发出了网络请求,那么回调就会被搞乱,最后一个 API 调用选项卡可能会控制回调。

对有状态小部件使用 AutomaticKeepAliveClientMixin,这是实现它的最简单方法,而无需将所有选项卡实例化在一起。

我的代码有一些接口,它们为调用选项卡提供各自的响应,我通过以下方式实现了它。

创建有状态小部件

class FollowUpsScreen extends StatefulWidget {
FollowUpsScreen();
        

@override
State<StatefulWidget> createState() {
return FollowUpsScreenState();
}
}
        

class FollowUpsScreenState extends State<FollowUpsScreen>
with AutomaticKeepAliveClientMixin<FollowUpsScreen>
implements OperationalControls {
    

@override
Widget build(BuildContext context) {
//do not miss this line
super.build(context);
return .....;
}


@override
bool get wantKeepAlive => true;
}

在导航栏底部保持标签状态的正确方法是用 PageStorage()小部件包装整个树,它以 PageStorageBucket bucket作为必需的命名参数,对于那些你想保持其状态的标签,那些受人尊敬的小部件使用 PageStorageKey(<str_key>),然后你就完成了!你可以看到更多的细节在这一年,我已经回答了几个星期的一个问题: https://stackoverflow.com/a/68620032/11974847

还有其他的选择,如 IndexedWidget(),但你应该注意当使用它,我已经解释了 y 我们应该谨慎,而使用 IndexedWidget()在给定的链接答案

祝你好运,伙计。

这个解决方案是基于 CupertinoTabScaffold的实现,它不会加载不必要的屏幕。

import 'package:flutter/material.dart';


enum MainPage { home, profile }


class BottomNavScreen extends StatefulWidget {
const BottomNavScreen({super.key});


@override
State<BottomNavScreen> createState() => _BottomNavScreenState();
}


class _BottomNavScreenState extends State<BottomNavScreen> {
var currentPage = MainPage.home;


@override
Widget build(BuildContext context) {
return Scaffold(
body: PageSwitchingView(
currentPageIndex: MainPage.values.indexOf(currentPage),
pageCount: MainPage.values.length,
pageBuilder: _pageBuilder,
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: MainPage.values.indexOf(currentPage),
onTap: (index) => setState(() => currentPage = MainPage.values[index]),
items: const [
BottomNavigationBarItem(
label: 'Home',
icon: Icon(Icons.home),
),
BottomNavigationBarItem(
label: 'Profile',
icon: Icon(Icons.account_circle),
),
],
),
);
}


Widget _pageBuilder(BuildContext context, int index) {
final page = MainPage.values[index];


switch (page) {
case MainPage.home:
return ...
case MainPage.profile:
return ...
}
}
}


/// A widget laying out multiple pages with only one active page being built
/// at a time and on stage. Off stage pages' animations are stopped.
class PageSwitchingView extends StatefulWidget {
const PageSwitchingView({
super.key,
required this.currentPageIndex,
required this.pageCount,
required this.pageBuilder,
});


final int currentPageIndex;
final int pageCount;
final IndexedWidgetBuilder pageBuilder;


@override
State<PageSwitchingView> createState() => _PageSwitchingViewState();
}


class _PageSwitchingViewState extends State<PageSwitchingView> {
final List<bool> shouldBuildPage = <bool>[];


@override
void initState() {
super.initState();
shouldBuildPage.addAll(List<bool>.filled(widget.pageCount, false));
}


@override
void didUpdateWidget(PageSwitchingView oldWidget) {
super.didUpdateWidget(oldWidget);


// Only partially invalidate the pages cache to avoid breaking the current
// behavior. We assume that the only possible change is either:
// - new pages are appended to the page list, or
// - some trailing pages are removed.
// If the above assumption is not true, some pages may lose their state.
final lengthDiff = widget.pageCount - shouldBuildPage.length;
if (lengthDiff > 0) {
shouldBuildPage.addAll(List<bool>.filled(lengthDiff, false));
} else if (lengthDiff < 0) {
shouldBuildPage.removeRange(widget.pageCount, shouldBuildPage.length);
}
}


@override
Widget build(BuildContext context) {
return Stack(
fit: StackFit.expand,
children: List<Widget>.generate(widget.pageCount, (int index) {
final active = index == widget.currentPageIndex;
shouldBuildPage[index] = active || shouldBuildPage[index];


return HeroMode(
enabled: active,
child: Offstage(
offstage: !active,
child: TickerMode(
enabled: active,
child: Builder(
builder: (BuildContext context) {
return shouldBuildPage[index] ? widget.pageBuilder(context, index) : Container();
},
),
),
),
);
}),
);
}
}