Flutter 三种方式实现页面切换后保持原页面状态

栏目: ASP.NET · 发布时间: 5年前

内容简介:在Flutter应用中,导航栏切换页面后默认情况下会丢失原页面状态,即每次进入页面时都会重新初始化状态,如果在在正文之前,先看一些常见的App导航,以喜马拉雅FM为例:它拥有一个固定的底部导航以及首页的顶部导航,可以看到不管是点击底部导航切换页面还是在首页左右侧滑切换页面,之前的页面状态都是始终维持的,下面就具体介绍下如何在flutter中实现类似喜马拉雅的导航效果

在Flutter应用中,导航栏切换页面后默认情况下会丢失原页面状态,即每次进入页面时都会重新初始化状态,如果在 initState 中打印日志,会发现每次进入时都会输出,显然这样增加了额外的开销,并且带来了不好的用户体验。

在正文之前,先看一些常见的App导航,以喜马拉雅FM为例:

Flutter 三种方式实现页面切换后保持原页面状态

它拥有一个固定的底部导航以及首页的顶部导航,可以看到不管是点击底部导航切换页面还是在首页左右侧滑切换页面,之前的页面状态都是始终维持的,下面就具体介绍下如何在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),
        )
    );
  }
}

复制代码

当前效果如下:

Flutter 三种方式实现页面切换后保持原页面状态

可以看到,从第二页切换回第一页时,第一页的状态已经丢失

第二步:实现底部导航切换时保持原页面状态

可能有些小伙伴在搜索后会开始直接使用官方推荐的 AutomaticKeepAliveClientMixin ,通过在子页面的State类重写 wantKeepAlivetrue 。 然而,如果你的代码和我上面的类似,body中并没有使用 PageViewTabBarView ,很确定的告诉你,踩到坑了,这样是无效的,原因后面再详述。现在我们先来介绍另外两种方式:

① 使用 IndexedStack 实现

IndexedStack 继承自 Stack ,它的作用是显示第 indexchild ,其它 child 在页面上是不可见的,但所有 child 的状态都被保持,所以这个 Widget 可以实现我们的需求,我们只需要将现在的 bodyIndexedStack 包裹一层即可

/// 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,
        ));
  }
复制代码

保存后再次测试一下

Flutter 三种方式实现页面切换后保持原页面状态

② 使用 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里,之所以采用那种写法纯粹是为了简洁直观<),如果在子页 StateinitState 中打印日志,可以在终端看到一次性输出了所有子页的日志。下面就介绍一下另一种通过 AutomaticKeepAliveClientMixin 方式来更好的实现

第三步:实现首页的顶部导航

前面提过在不配合使用 PageViewTabBarView 时,单独使用 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),
        ));
  }
}

复制代码

保存后测试,

Flutter 三种方式实现页面切换后保持原页面状态

可以看到,现在添加了首页顶部导航,且默认支持左右侧滑,接下来再进一步的完善状态保持

第四步:实现首页顶部导航切换时保持原页面状态

③ 使用 AutomaticKeepAliveClientMixin 实现

写到这里已经很简单了,我们只需要在首页导航内需要保持页面状态的子页 State 中,继承 AutomaticKeepAliveClientMixin 并重写 wantKeepAlivetrue 即可,以首页推荐 recommend_page.dart 为例:

/// recommend_page.dart
class _RecommendPageState extends State<RecommendPage>
    with AutomaticKeepAliveClientMixin {
    
  @override
  bool get wantKeepAlive => true;
  ...
  ...
  ...
}

复制代码

再次保存测试,

Flutter 三种方式实现页面切换后保持原页面状态

Ok,至此已经实现了类似喜马拉雅的底部导航+首页顶部导航 ~


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

精益数据分析

精益数据分析

[加] 阿利斯泰尔·克罗尔、[加] 本杰明·尤科维奇 / 韩知白、王鹤达 / 人民邮电出版社 / 2014-12 / 79.00元

本书展示了如何验证自己的设想、找到真正的客户、打造能赚钱的产品,以及提升企业知名度。30多个案例分析,全球100多位知名企业家的真知灼见,为你呈现来之不易、经过实践检验的创业心得和宝贵经验,值得每位创业家和企业家一读。 深入理解精益创业、数据分析基础,和数据驱动的思维模式 如何将六个典型的商业模式应用到各种规模的新企业 找到你的第一关键指标 确定底线,找到出发点 在大......一起来看看 《精益数据分析》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具