Flutter切换底部导航, 怎样保持子页面状态?
前言
产品设计时, 一款APP使用底部导航结构, 是司空见惯的, 闲鱼做为国内第一个全Flutter产品, 也使用的是底部导航结构。平常开发过程中, 开发者只要按照官方文档的要求, 使用底部导航的控件就可以轻松实现一个底部导航的APP。但是, 最近在浏览flutter论坛时, 发现一个关于底部导航切换, 如何保持子页面状态的问题? 笔者带着疑问, 一起来探究下。
问题复现
复现子页面重新创建
main.dart
class IndexStackApp extends StatefulWidget { const IndexStackApp({Key? key}) : super(key: key); @override _IndexStackAppState createState() => _IndexStackAppState(); } class _IndexStackAppState extends State<IndexStackApp> { int _currentIndex = 0; final List<BottomNavigationBarItem> _bottomNavItems = [ BottomNavigationBarItem( backgroundColor: Colors.blue, icon: Icon(Icons.home), title: Text("首页"), ), BottomNavigationBarItem( backgroundColor: Colors.green, icon: Icon(Icons.message), title: Text("消息"), ), BottomNavigationBarItem( backgroundColor: Colors.amber, icon: Icon(Icons.shopping_cart), title: Text("购物车"), ), ]; final _pages = [ APage(), BPage(), CPage(), ]; @override Widget build(BuildContext context) { print('------main, ----build'); return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: Scaffold( /// 1、这种方式, 得到的子widget每次都会重新build, 也就是重新绘制 body: _pages[_currentIndex], bottomNavigationBar: BottomNavigationBar( items: _bottomNavItems, currentIndex: _currentIndex, onTap: (index) { setState(() { _currentIndex = index; }); }, ), ), ); } }复制代码
a.dart
import 'package:flutter/material.dart'; class APage extends StatefulWidget { const APage({Key? key}) : super(key: key); @override _APageState createState() => _APageState(); } class _APageState extends State<APage> { @override void initState() { // TODO: implement initState super.initState(); print('------a, initState'); } @override Widget build(BuildContext context) { print('------a----build'); return Container( width: double.infinity, height: double.infinity, color: Colors.indigoAccent, child: Center( child: Text('aaaaaaaaaaaaaaaaaaaaaa'), ) ); } @override void deactivate() { // TODO: implement deactivate super.deactivate(); print('------a----deactivate'); } @override void dispose() { // TODO: implement dispose super.dispose(); print('------a----dispose'); } }复制代码
b.dart
import 'package:flutter/material.dart'; class BPage extends StatefulWidget { const BPage({Key? key}) : super(key: key); @override _BPageState createState() => _BPageState(); } class _BPageState extends State<BPage> { @override void initState() { // TODO: implement initState super.initState(); print('------b, initState'); } @override Widget build(BuildContext context) { print('------b----build'); return Container( width: double.infinity, height: double.infinity, color: Colors.amber, child: Center( child: Text('bbbbbbbbbbbbbbbbbbbbbbbbbbbb'), ) ); } @override void deactivate() { // TODO: implement deactivate super.deactivate(); print('------b----deactivate'); } @override void dispose() { // TODO: implement dispose super.dispose(); print('------b----dispose'); } }复制代码
c.dart
import 'package:flutter/material.dart'; class CPage extends StatefulWidget { const CPage({Key? key}) : super(key: key); @override _CPageState createState() => _CPageState(); } class _CPageState extends State<CPage> { @override void initState() { // TODO: implement initState super.initState(); print('------c, initState'); } @override Widget build(BuildContext context) { print('------c----build'); return Container( width: double.infinity, height: double.infinity, color: Colors.indigoAccent, child: Center( child: Text('ccccccccccccccccccc'), ) ); } @override void deactivate() { // TODO: implement deactivate super.deactivate(); print('------c----deactivate'); } @override void dispose() { // TODO: implement dispose super.dispose(); print('------c----dispose'); } }复制代码
上面的实现方式, body: _pages[_currentIndex]
。每次根据下标_currentIndex
从页面数组_pages
取出对应的子页面时, 都会重新创建页面。
我们看下控制台的日志。
从日志可以看出, 程序启动后的顺序。
会先在
main.dart
中根据下标_currentIndex = 0
取出APage
APage
会创建并执行initState
、build
方法。此时, 点击底部导航按钮
购物车
, 程序会先执行APage
的deactivate
使APage
处于失活状态再执行
CPage
的initState
新建CPage
然后执行
CPage
的build
执行
APage
的dispose
, 销毁APage
解决方案
频繁新建子页面, 这样显然是有缺陷的, 因为会耗费性能。
IndexedStack
因为, 笔者用的是IndexedStack
来实现底部导航的, 所以, 接下来, 我们一起看下。上面main.dart
的build
方法可以改成如下实现方式。
class IndexStackApp extends StatefulWidget { const IndexStackApp({Key? key}) : super(key: key); @override _IndexStackAppState createState() => _IndexStackAppState(); } class _IndexStackAppState extends State<IndexStackApp> { int _currentIndex = 0; final List<BottomNavigationBarItem> _bottomNavItems = [ BottomNavigationBarItem( backgroundColor: Colors.blue, icon: Icon(Icons.home), title: Text("首页"), ), BottomNavigationBarItem( backgroundColor: Colors.green, icon: Icon(Icons.message), title: Text("消息"), ), BottomNavigationBarItem( backgroundColor: Colors.amber, icon: Icon(Icons.shopping_cart), title: Text("购物车"), ), ]; final _pages = [ APage(), BPage(), CPage(), ]; @override Widget build(BuildContext context) { print('------main, ----build'); return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: Scaffold( /// 2、这种方式, 得到的子widget不会每次重新build body: IndexedStack( index: _currentIndex, children: _pages, ), bottomNavigationBar: BottomNavigationBar( items: _bottomNavItems, currentIndex: _currentIndex, onTap: (index) { setState(() { _currentIndex = index; }); }, ), ), ); } }复制代码
使用IndexedStack
实现底部导航, 点击购物车
, 我们一起看看控制台日志。
main.dart
, 程序运行后, 会一次性全部加载APage
、BPage
、CPage
三个子页面点击底部导航
购物车
, 程序只是执行了main.dart
的build
方法, 并没有加载三个子页面的任何一个
由此可知, 通过IndexedStack
组件, 点击底部导航, 根据下标index
, 只展示对应的子页面即可, 继续保持子页面状态。
作者:Nicholas68
链接:https://juejin.cn/post/7039537981002088455