阅读 181

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取出对应的子页面时, 都会重新创建页面。

c.png

我们看下控制台的日志。

子widget保存状态.png

从日志可以看出, 程序启动后的顺序。

  • 会先在main.dart中根据下标_currentIndex = 0取出APage

  • APage会创建并执行initStatebuild方法。

  • 此时, 点击底部导航按钮购物车, 程序会先执行APagedeactivate使APage处于失活状态

  • 再执行CPageinitState新建CPage

  • 然后执行CPagebuild

  • 执行APagedispose, 销毁APage

解决方案

频繁新建子页面, 这样显然是有缺陷的, 因为会耗费性能。

IndexedStack

因为, 笔者用的是IndexedStack来实现底部导航的, 所以, 接下来, 我们一起看下。上面main.dartbuild方法可以改成如下实现方式。

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实现底部导航, 点击购物车, 我们一起看看控制台日志。

c-.png

  • main.dart, 程序运行后, 会一次性全部加载APageBPageCPage三个子页面

  • 点击底部导航购物车, 程序只是执行了main.dartbuild方法, 并没有加载三个子页面的任何一个

由此可知, 通过IndexedStack组件, 点击底部导航, 根据下标index, 只展示对应的子页面即可, 继续保持子页面状态。


作者:Nicholas68
链接:https://juejin.cn/post/7039537981002088455


文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐