内容简介:在Flutter应用中,导航栏切换页面后默认情况下会丢失原页面状态,即每次进入页面时都会重新初始化状态,如果在在正文之前,先看一些常见的App导航,以喜马拉雅FM为例:它拥有一个固定的底部导航以及首页的顶部导航,可以看到不管是点击底部导航切换页面还是在首页左右侧滑切换页面,之前的页面状态都是始终维持的,下面就具体介绍下如何在flutter中实现类似喜马拉雅的导航效果
在Flutter应用中,导航栏切换页面后默认情况下会丢失原页面状态,即每次进入页面时都会重新初始化状态,如果在 initState
中打印日志,会发现每次进入时都会输出,显然这样增加了额外的开销,并且带来了不好的用户体验。
在正文之前,先看一些常见的App导航,以喜马拉雅FM为例:
它拥有一个固定的底部导航以及首页的顶部导航,可以看到不管是点击底部导航切换页面还是在首页左右侧滑切换页面,之前的页面状态都是始终维持的,下面就具体介绍下如何在flutter中实现类似喜马拉雅的导航效果
第一步:实现固定的底部导航
在通过 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),
)
);
}
}
复制代码
当前效果如下:
可以看到,从第二页切换回第一页时,第一页的状态已经丢失
第二步:实现底部导航切换时保持原页面状态
可能有些小伙伴在搜索后会开始直接使用官方推荐的 AutomaticKeepAliveClientMixin
,通过在子页面的State类重写 wantKeepAlive
为 true
。 然而,如果你的代码和我上面的类似,body中并没有使用 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
方式来更好的实现
第三步:实现首页的顶部导航
前面提过在不配合使用 PageView
或 TabBarView
时,单独使用 AutomaticKeepAliveClientMixin
是无效的,下面我们就通过配合使用 TabBarView
来实现顶部导航。首先我们在 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) {
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
Widget build(BuildContext context) {
return Scaffold(
body:
Center(child: Text('首页推荐: $count', style: TextStyle(fontSize: 30))),
floatingActionButton: FloatingActionButton(
onPressed: add,
child: Icon(Icons.add),
));
}
}
复制代码
保存后测试,
可以看到,现在添加了首页顶部导航,且默认支持左右侧滑,接下来再进一步的完善状态保持
第四步:实现首页顶部导航切换时保持原页面状态
③ 使用 AutomaticKeepAliveClientMixin
实现
写到这里已经很简单了,我们只需要在首页导航内需要保持页面状态的子页 State
中,继承 AutomaticKeepAliveClientMixin
并重写 wantKeepAlive
为 true
即可,以首页推荐 recommend_page.dart
为例:
/// recommend_page.dart
class _RecommendPageState extends State<RecommendPage>
with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
...
...
...
}
复制代码
再次保存测试,
Ok,至此已经实现了类似喜马拉雅的底部导航+首页顶部导航 ~
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- flutter实战6:TAB页面切换免重绘
- 给React-Router添加路由页面切换时的过渡动画
- python web py入门(71)- jQuery - 通过键盘实现页面显示字体的格式切换
- Flutter 特定页面切换屏幕方向/iOS强制横屏/SystemChrome.setPreferredOrientations不起作用 看这里!
- Egret场景切换管理类切换和单例使用方法
- Spring项目中使用两种方法动态切换数据源,多数据源切换
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
R语言实战(第2版)
[美] Robert I. Kabacoff / 王小宁、刘撷芯、黄俊文等 / 人民邮电出版社 / 2016-5 / 99.00元
本书注重实用性,是一本全面而细致的R指南,高度概括了该软件和它的强大功能,展示了使用的统计示例,且对于难以用传统方法处理的凌乱、不完整和非正态的数据给出了优雅的处理方法。作者不仅仅探讨统计分析,还阐述了大量探索和展示数据的图形功能。新版做了大量更新和修正,新增了近200页内容,介绍数据挖掘、预测性分析和高级编程。一起来看看 《R语言实战(第2版)》 这本书的介绍吧!