Предисловие:
В приложениях Flutter состояние исходной страницы по умолчанию будет утеряно после переключения страниц панелью навигации, то есть состояние будет повторно инициализироваться каждый раз при входе на страницу.initState
Если вы распечатаете журнал, вы обнаружите, что он будет выводиться каждый раз, когда вы входите, что, очевидно, добавляет дополнительные накладные расходы и создает плохой пользовательский опыт.
Прежде чем приступить к тексту, давайте рассмотрим некоторые распространенные способы навигации в приложении на примере Himalaya FM:
Он имеет фиксированную нижнюю навигацию и верхнюю навигацию домашней страницы.Вы можете видеть, что независимо от того, щелкаете ли вы нижнюю навигацию для переключения страниц или проводите влево и вправо на домашней странице для переключения страниц, предыдущий статус страницы всегда сохраняется. гималайский навигационный эффект в
Шаг 1. Внедрите фиксированную нижнюю навигацию.
проходя черезflutter create
В сгенерированном шаблоне проекта давайте сначала упростим код,MyHomePage
извлечь в отдельныйhome.dart
файл, и вScaffold
Добавлены лесаbottomNavigationBar
нижняя навигация, вbody
Отображает текущую выбранную подстраницу.
/// home.dart
import 'package:flutter/material.dart';
import './pages/first_page.dart';
import './pages/second_page.dart';
import './pages/third_page.dart';
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final items = [
BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('首页')),
BottomNavigationBarItem(icon: Icon(Icons.music_video), title: Text('听')),
BottomNavigationBarItem(icon: Icon(Icons.message), title: Text('消息'))
];
final bodyList = [FirstPage(), SecondPage(), ThirdPage()];
int currentIndex = 0;
void onTap(int index) {
setState(() {
currentIndex = index;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('demo'),
),
bottomNavigationBar: BottomNavigationBar(
items: items,
currentIndex: currentIndex,
onTap: onTap
),
body: bodyList[currentIndex]
);
}
}
Три подстраницы имеют одинаковую структуру, показывая счетчик и кнопку «плюс» дляfirst_page.dart
Например:
/// first_page.dart
import 'package:flutter/material.dart';
class FirstPage extends StatefulWidget {
@override
_FirstPageState createState() => _FirstPageState();
}
class _FirstPageState extends State<FirstPage> {
int count = 0;
void add() {
setState(() {
count++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text('First: $count', style: TextStyle(fontSize: 30))
),
floatingActionButton: FloatingActionButton(
onPressed: add,
child: Icon(Icons.add),
)
);
}
}
Текущий эффект выглядит следующим образом:
Видно, что при переходе со второй страницы обратно на первую страницу состояние первой страницы сбилось
Шаг 2. Сохраните исходное состояние страницы при реализации нижнего навигационного переключателя
Возможно, некоторые друзья начнут пользоваться официальной рекомендацией сразу после поиска.AutomaticKeepAliveClientMixin
, переопределив класс State на дочерней страницеwantKeepAlive
дляtrue
. Однако, если ваш код похож на мой выше, тело не используетPageView
илиTabBarView
, мне жаль говорить вам, что если вы наступите на яму, это недействительно, и причины будут подробно описаны позже. Теперь давайте представим два других метода:
① использоватьIndexedStack
выполнить
IndexedStack
унаследовано отStack
, его роль заключается в отображении первогоindex
индивидуальныйchild
,разноеchild
не видно на странице, но всеchild
состояние сохраняется, так что этоWidget
Для достижения наших нужд нам нужно всего лишь изменить текущуюbody
использоватьIndexedStack
Просто заверните это
/// home.dart
class _MyHomePageState extends State<MyHomePage> {
...
...
...
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('demo'),
),
bottomNavigationBar: BottomNavigationBar(
items: items, currentIndex: currentIndex, onTap: onTap),
// body: bodyList[currentIndex]
body: IndexedStack(
index: currentIndex,
children: bodyList,
));
}
протестировать снова после сохранения
② использоватьOffstage
выполнить
Offstage
Роль очень простая, управляется параметромchild
Отображать ли, чтобы мы также могли использовать в сочетанииOffstage
Для достижения этого требования принцип реализации такой же, какIndexedStack
аналогичный
/// home.dart
class _MyHomePageState extends State<MyHomePage> {
...
...
...
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('demo'),
),
bottomNavigationBar: BottomNavigationBar(
items: items, currentIndex: currentIndex, onTap: onTap),
// body: bodyList[currentIndex],
body: Stack(
children: [
Offstage(
offstage: currentIndex != 0,
child: bodyList[0],
),
Offstage(
offstage: currentIndex != 1,
child: bodyList[1],
),
Offstage(
offstage: currentIndex != 2,
child: bodyList[2],
),
],
));
}
}
Вышеуказанными двумя способами может быть достигнута необходимость сохранения состояния исходной страницы, но есть некоторые накладные расходы.Опытные партнеры должны быть в состоянии обнаружить, что при первой загрузке приложения состояние всех дочерних создается экземпляр страницы (> подробности здесь не потому, что я поместил создание экземпляра подстраницы непосредственно вbodyList
внутри...StateизinitState
Распечатайте журнал в терминале, и вы увидите, что журнал выводит все подстраницы одновременно в терминал. Вот еще один способ передать наследствоAutomaticKeepAliveClientMixin
способ лучше достичь сохранения состояния.
Шаг 3. Реализуйте верхнюю навигацию на главной странице
Во-первых, мы используемTabBar
+TabBarView
+AutomaticKeepAliveClientMixin
для реализации верхней навигации (примечание:TabBar
а такжеTabBarView
необходимо предоставитьcontroller
, если вы не определяете его сами, вы должны использоватьDefaultTabController
пакет). Здесь вы также можете использоватьPageView
, который будет представлен позже.
мы первыеhome.dart
удаление файлаScaffold
в строительных лесахappBar
Верхняя панель инструментов, затем начните переписывать домашнюю страницуfirst_page.dart
:
/// first_page.dart
import 'package:flutter/material.dart';
import './recommend_page.dart';
import './vip_page.dart';
import './novel_page.dart';
import './live_page.dart';
class _TabData {
final Widget tab;
final Widget body;
_TabData({this.tab, this.body});
}
final _tabDataList = <_TabData>[
_TabData(tab: Text('推荐'), body: RecommendPage()),
_TabData(tab: Text('VIP'), body: VipPage()),
_TabData(tab: Text('小说'), body: NovelPage()),
_TabData(tab: Text('直播'), body: LivePage())
];
class FirstPage extends StatefulWidget {
@override
_FirstPageState createState() => _FirstPageState();
}
class _FirstPageState extends State<FirstPage> {
final tabBarList = _tabDataList.map((item) => item.tab).toList();
final tabBarViewList = _tabDataList.map((item) => item.body).toList();
@override
Widget build(BuildContext context) {
super.build(context);
return DefaultTabController(
length: tabBarList.length,
child: Column(
children: <Widget>[
Container(
width: double.infinity,
height: 80,
padding: EdgeInsets.fromLTRB(20, 24, 0, 0),
alignment: Alignment.centerLeft,
color: Colors.black,
child: TabBar(
isScrollable: true,
indicatorColor: Colors.red,
indicatorSize: TabBarIndicatorSize.label,
unselectedLabelColor: Colors.white,
unselectedLabelStyle: TextStyle(fontSize: 18),
labelColor: Colors.red,
labelStyle: TextStyle(fontSize: 20),
tabs: tabBarList),
),
Expanded(
child: TabBarView(
children: tabBarViewList,
// physics: NeverScrollableScrollPhysics(), // 禁止滑动
))
],
));
}
}
Структура рекомендуемой страницы, VIP-страницы, новой страницы и страницы прямой трансляции по-прежнему такая же, как и структура предыдущей домашней страницы, отображаются только счетчик и кнопка «плюс», чтобы указать рекомендуемую страницу.recommend_page.dart
Например:
/// recommend_page.dart
import 'package:flutter/material.dart';
class RecommendPage extends StatefulWidget {
@override
_RecommendPageState createState() => _RecommendPageState();
}
class _RecommendPageState extends State<RecommendPage> {
int count = 0;
void add() {
setState(() {
count++;
});
}
@override
void initState() {
super.initState();
print('recommend initState');
}
@override
Widget build(BuildContext context) {
return Scaffold(
body:Center(
child: Text('首页推荐: $count', style: TextStyle(fontSize: 30))
),
floatingActionButton: FloatingActionButton(
onPressed: add,
child: Icon(Icons.add),
));
}
}
тест после сохранения,
Видно, что теперь добавлена верхняя навигация домашней страницы, а левый и правый слайды поддерживаются по умолчанию, а дальше состояние будет улучшаться.Шаг 4. Сохраняйте исходное состояние страницы при переключении верхней навигации на главной странице.
③ ИспользованиеAutomaticKeepAliveClientMixin
выполнить
Здесь очень просто написать, нам нужны только подстраницы, которые должны поддерживать статус страницы в навигации по главной странице.State
в, наследствоAutomaticKeepAliveClientMixin
и переписатьwantKeepAlive
дляtrue
Вот и все.
notes:Subclasses must implement wantKeepAlive, and their build methods must call super.build (the return value will always return null, and should be ignored)
Рекомендовано главной страницейrecommend_page.dart
Например:
/// recommend_page.dart
import 'package:flutter/material.dart';
class RecommendPage extends StatefulWidget {
@override
_RecommendPageState createState() => _RecommendPageState();
}
class _RecommendPageState extends State<RecommendPage>
with AutomaticKeepAliveClientMixin {
int count = 0;
void add() {
setState(() {
count++;
});
}
@override
bool get wantKeepAlive => true;
@override
void initState() {
super.initState();
print('recommend initState');
}
@override
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
body:Center(
child: Text('首页推荐: $count', style: TextStyle(fontSize: 30))
),
floatingActionButton: FloatingActionButton(
onPressed: add,
child: Icon(Icons.add),
));
}
}
Сохраните тест еще раз,
Теперь видно, что независимо от того, переключается ли нижняя навигация или переключается на верхнюю навигацию домашней страницы, все состояния страницы могут сохраняться, и когда приложение загружается в первый раз, терминал видит толькоrecommend initState
Журнал первого перехода в верхнюю часть домашней страницы и перехода на вип-страницу, терминал выводитvip initState
, при повторном возврате на рекомендованную страницу больше не выводитьrecommend initState
.
Итак, используйтеTabBarView
+AutomaticKeepAliveClientMixin
Этот метод не только реализует поддержку состояния страницы, но также имеет функцию, аналогичную ленивому вычислению: он не создает экземпляр неиспользуемого состояния страницы, что снижает накладные расходы на инициализацию приложения.
возобновить
Использование нижней навигации было описано ранееIndexedStack
а такжеOffstage
Есть два способа поддерживать состояние страницы, но их недостаток в том, что все дочерние страницы создаются при первой загрузке.State
. Для дальнейшей оптимизации ниже используемPageView
+AutomaticKeepAliveClientMixin
Перепишите предыдущую нижнюю навигацию, гдеPageView
а такжеTabBarView
Принцип реализации аналогичен, и нет обязательного требования, какой из них выбрать. после обновленияhome.dart
Файлы следующие:
/// home.dart
import 'package:flutter/material.dart';
import './pages/first_page.dart';
import './pages/second_page.dart';
import './pages/third_page.dart';
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final items = [
BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('首页')),
BottomNavigationBarItem(icon: Icon(Icons.music_video), title: Text('听')),
BottomNavigationBarItem(icon: Icon(Icons.message), title: Text('消息'))
];
final bodyList = [FirstPage(), SecondPage(), ThirdPage()];
final pageController = PageController();
int currentIndex = 0;
void onTap(int index) {
pageController.jumpToPage(index);
}
void onPageChanged(int index) {
setState(() {
currentIndex = index;
});
}
@override
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
bottomNavigationBar: BottomNavigationBar(
items: items, currentIndex: currentIndex, onTap: onTap),
// body: bodyList[currentIndex],
body: PageView(
controller: pageController,
onPageChanged: onPageChanged,
children: bodyList,
physics: NeverScrollableScrollPhysics(), // 禁止滑动
));
}
}
затем вbodyList
подстраницыState
НаследованиеAutomaticKeepAliveClientMixin
и переписатьwantKeepAlive
,кsecond_page.dart
Например:
/// second_page.dart
import 'package:flutter/material.dart';
class SecondPage extends StatefulWidget {
@override
_SecondPageState createState() => _SecondPageState();
}
class _SecondPageState extends State<SecondPage>
with AutomaticKeepAliveClientMixin {
int count = 0;
void add() {
setState(() {
count++;
});
}
@override
bool get wantKeepAlive => true;
@override
void initState() {
super.initState();
print('second initState');
}
@override
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
body: Center(
child: Text('Second: $count', style: TextStyle(fontSize: 30))
),
floatingActionButton: FloatingActionButton(
onPressed: add,
child: Icon(Icons.add),
));
}
}
Ок, сохраняем и запускаем после обновления, приложение не выведется при первой загрузкеsecond initState
, только при первом щелчке нижней навигации для переключения на страницу подстраницаState
создается экземпляр.
Итак, как реализовать аналогичную навигацию внизу + на главной странице. Конец ~