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 {
  // 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
    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 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,
      ),

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

页面的状态被保持了下来

你可能感兴趣的:(Flutter代码锦囊---切换时页面保持状态)