Flutter代码锦囊---切换时页面保持状态

一个正常的Flutter项目中,通过底部导航栏(BottomNavigationBar)或者标签栏(TabBar)组件来切换页面内容,是很正常的操作。但是大家是否有发现,每次导航栏或标签栏切换页面时,之前的页面就被清理了。比如,第一个页面的列表视图(ListView)已经滑动到底部,切换到第二个页面以后再回来,第一个页面的列表又回到了顶部。

出现上面问题的原因是,页面的状态(State)没有被保留下来,所以每次页面,都会初始化一次状态。我们可以通过自动保持活动客户端混合(AutomaticKeepAliveClientMixin)抽象类来解决这个问题。

首先是新建一个dart文件,把Flutter应用和页面的架子搭起来,然后运行调试,确认应用可以正常跑起来。

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: MyHomePage(title: 'Flutter Demo 主页'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  // TODO:当前页面视图的相关变量。

  // TODO:切换页面视图的方法。

  // TODO:集中管理导航项目列表和页面组件列表。

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // TODO:页面视图的主要内容。
    );
  }
}

下面再新建一个dart文件,作为导航项对应的页面组件,并再里面放上一个列表视图(ListView)组件,用来实验后面的代码是否实现保持页面的状态(State)。

自动保持活动客户端混合(AutomaticKeepAliveClientMixin)抽象类,为自动保持活动(AutomaticKeepAlive)的客户端提供直接可以使用的方法,与状态(State)子类一起使用,可以避免作为父组件的页面视图(PageView)组件切换时被重新绘制。

自动保持活动客户端混合(AutomaticKeepAliveClientMixin)抽象类的想要保持活动(wantKeepAlive)属性,用于设置当前实例是否应保持活动状态,不因父组件的切换而重新绘制。这一步很关键。

/// 自定义的导航页面组件。
class NavigationPage extends StatefulWidget {
  @override
  _NavigationPageState createState() => _NavigationPageState();
}

/// 与自定义的导航页面组件关联的状态子类。
class _NavigationPageState extends State<NavigationPage>
    with AutomaticKeepAliveClientMixin {

  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return Scaffold(
      body: ListView.builder(
        padding: EdgeInsets.all(8.0),
        itemExtent: 20.0,
        itemBuilder: (BuildContext context, int index) {
          return Text('项目 $index');
        },
      ),
    );
  }
}

接下来要配置底部导航栏(BottomNavigationBar)组件的项目(items)属性需要的项目列表,以及对应的页面组件。

  // TODO:集中管理导航项目列表和页面组件列表。
  /// 统一管理导航项目的列表。
  List<BottomNavigationBarItem> navigationItem = [
    BottomNavigationBarItem(
      icon: Icon(Icons.home),
      title: Text('首页'),
    ),
    BottomNavigationBarItem(
      icon: Icon(Icons.comment),
      title: Text('消息'),
    ),
    BottomNavigationBarItem(
      icon: Icon(Icons.settings),
      title: Text('设置'),
    ),
  ];

  /// 统一管理导航项目对应的组件列表。
  final _widgetOptions = [
    NavigationPage(),
    NavigationPage(),
    NavigationPage(),
  ];

然后定义一下页面视图(PageView)组件的选择索引、控制器及资源释放函数。

  // TODO:当前页面视图的相关变量。
  /// 当前页面选择的索引。
  int _selectedIndex = 0;

  /// 页面控制器(`PageController`)组件,页面视图(`PageView`)的控制器。
  PageController _controller = PageController();

  /// 释放相关资源。
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

因为我们使用来页面视图(PageView)组件,而页面视图组件不会自动切换页面,因此我们要自己写一下切换页面的方法。

  // TODO:切换页面视图的方法。
  void _onItemTapped(int index) {
    // 创建底部导航栏的组件需要跟踪当前索引并调用`setState`以使用新提供的索引重建它。
    setState(() {
      _selectedIndex = index;
      // 跳到页面(`jumpToPage`)方法,更改显示在的页面视图(`PageView`)组件中页面。
      _controller.jumpToPage(index);
    });
  }

最后完成脚手架(Scaffold)组件上的主体内容,把上面的内容都派上用场。

      // TODO:页面视图的主要内容。
      // 页面视图(`PageView`)组件,可逐页工作的可滚动列表,每个子项都被强制与视窗大小相同。
      body: PageView.builder(
        // 物理(`physics`)属性,页面视图应如何响应用户输入。
        // 从不可滚动滚动物理(`NeverScrollableScrollPhysics`)类,不允许用户滚动。
        physics: NeverScrollableScrollPhysics(),
        itemBuilder: (BuildContext context, int index) {
          return _widgetOptions.elementAt(index);
        },
        itemCount: _widgetOptions.length,
        // 控制器(`controller`)属性,用于控制滚动此页面视图位置的对象。
        controller: _controller,
      ),
      // 底部导航栏(`bottomNavigationBar`)属性,显示在脚手架(`Scaffold`)组件的底部。
      // 底部导航栏(`BottomNavigationBar`)组件,显示在应用程序底部的组件,
      // 用于在几个屏幕之间中进行选择,通常在三到五之间,再多就不好看了。
      bottomNavigationBar: BottomNavigationBar(
        // 项目(`items`)属性,位于底部导航栏中的交互组件,其中每一项都有一个图标和标题。
        items: navigationItem,
        // 目前的索引(`currentIndex`)属性,当前活动项的项目索引。
        currentIndex: _selectedIndex,
        // 固定颜色(`fixedColor`)属性,当BottomNavigationBarType.fixed时所选项目的颜色。
        fixedColor: Color(0xffFE7C30),
        // 在点击(`onTap`)属性,点击项目时调用的回调。
        onTap: _onItemTapped,
        // 定义底部导航栏(`BottomNavigationBar`)组件的布局和行为。
        type: BottomNavigationBarType.fixed,
      ),

运行代码,实现效果如下面的图片。

页面的状态被保持了下来
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容