Как сохранить состояния виджетов во флаттере при навигации с помощью BottomNavigationBar?
В настоящее время я работаю над созданием приложения Flutter, которое будет сохранять состояния при переходе с одного экрана на другой и обратно при использовании BottomNavigationBar. Так же, как это работает в мобильном приложении Spotify; если вы перешли вниз до определенного уровня в иерархии навигации на одном из главных экранов, изменение экрана через нижнюю панель навигации, а затем возврат к старому экрану сохранят положение пользователя в этой иерархии, включая сохранение штат.
Я прислонилась головой к стене, безуспешно пробуя разные вещи.
Я хочу знать, как я могу предотвратить страницы в pageChooser()
, когда переключается, когда пользователь касается элемента BottomNavigationBar, не восстанавливая себя, а вместо этого сохраняет состояние, в котором он уже находился (все страницы являются виджетами с состоянием).
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)
)
],
)
)
);
}
}
10 ответов
Для сохранения состояния в 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,
),
);
}
Поздно к вечеринке, но у меня есть простое решение. Использовать
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);
});
},
...
}
Индивидуальная вкладка:
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);
...
}
}
Я снял об этом видео здесь.
Используйте 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) {
return Container();
}
}
Вместо того, чтобы возвращать новый экземпляр каждый раз, когда вы запускаете 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)
)
],
)
)
);
}
}
Или вы можете использовать виджеты, такие как PageView
или же Stack
добиться того же.
Надеюсь, это поможет!
Используйте " Виджет IndexedStack " с " Виджетом нижней панели навигации ", чтобы сохранить состояние экранов / страниц / виджета
Предоставьте список виджетов в IndexedStack и индекс виджетов, которые вы хотите показать, потому что IndexedStack показывает один виджет из списка одновременно.
final List<Widget> _children = [
FirstClass(),
SecondClass()
];
Scaffold(
body: IndexedStack(
index: _selectedPage,
children: _children,
),
bottomNavigationBar: BottomNavigationBar(
........
........
),
);
Самый удобный способ, который я нашел, - использовать виджет PageStorage вместе с PageStorageBucket, который действует как постоянный слой ключевого значения.
Прочтите эту статью, чтобы получить красивое объяснение -> https://steemit.com/utopian-io/@tensor/persisting-user-interface-state-and-building-bottom-navigation-bars-in-dart-s-flutter-framework
Не используйте 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;
}
Краткое изложение решений: предложены два подхода
Решение 1. АвтоматическийKeepAliveClientMixin
class SubPage extends StatefulWidget {
@override
State<SubPage> createState() => _SubPageState();
}
class _SubPageState extends State<SubPage> with AutomaticKeepAliveClientMixin {
@override
Widget build(BuildContext context) {
super.build(context); // Ensure that the mixin is initialized
return Container();
}
@override
bool get wantKeepAlive => true;
}
/*-------------------------------*/
class MainPage extends StatefulWidget {
const MainPage({Key? key}) : super(key: key);
@override
State<MainPage> createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
int currentBottom = 0;
static const List<Widget> _page = [
SubPage(),
SubPage(),
];
@override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: BottomNavigationBar(
currentIndex: currentBottom,
onTap: (index) => setState(() {
currentBottom = index;
}),
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home)),
BottomNavigationBarItem(icon: Icon(Icons.settings))
],
),
body: _page.elementAt(currentBottom));
}
}
Решение 2. Индексированный стек
class MainPage extends StatefulWidget {
@override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
int _currentIndex = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: _currentIndex,
children: <Widget>[
Page1(),
Page2(),
Page3(),
],
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (int index) {
setState(() {
_currentIndex = index;
});
},
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Page 1',
),
BottomNavigationBarItem(
icon: Icon(Icons.search),
label: 'Page 2',
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: 'Page 3',
),
],
),
);
}
}
/*--------------------------------*/
class Page1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
// Page 1 content
);
}
}
// Repeat the above for Page2 and Page3
правильный способ сохранить состояние вкладок на нижней панели навигации - обернуть все дерево
PageStorage()
виджет, который принимает
PageStorageBucket bucket
в качестве обязательного именованного параметра и для тех вкладок, для которых вы хотите сохранить его состояние, вместо уважаемых виджетов с
PageStorageKey(<str_key>)
тогда все готово !! вы можете увидеть более подробную информацию в этом ответе, на который я ответил несколько недель назад на один вопрос: /questions/53428616/flutter-bottomnavigator-s-indeksirovannyim-stekom/58558227#58558227
есть и другие альтернативы, такие как, но вы должны остерегаться его использования, я уже объяснил, что мы должны проявлять осторожность при использовании
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();
},
),
),
),
);
}),
);
}
}