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

前言:
在 Flutter 应用中,导航栏切换页面后默认情况下会丢失原页面状态,即每次进入页面时都会重新初始化状态,不仅增加额外开销,而且体验差。

  1. 使用IndexedStack实现

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

class MainDart extends StatefulWidget {

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

class _MainDartState extends State<MainDart> with TickerProviderStateMixin{

  //默认索引
  int positionIndex = 0;
  //底部导航栏
  var mainTitles = ['患者诊疗', '收费','物资', '设置'];
  var indexStack;
  List<BottomNavigationBarItem> navigationViews;

  @override
  Widget build(BuildContext context) {
    initData();
    return Scaffold(
      appBar: PreferredSize(
        preferredSize:Size.fromHeight(MediaQuery.of(context).size.height * 0.07),
        child:SafeArea(
          top: true,
          child: Offstage(),
        ),
      ),
      body: indexStack,
      bottomNavigationBar: initNavigationBar(),
    );
  }
  @override
  void initState() {
    super.initState();
    navigationViews = <BottomNavigationBarItem>[
      new BottomNavigationBarItem(
        icon: const Icon(Icons.home),
        title: new Text(mainTitles[0]),
        backgroundColor: Colors.black,
      ),
      new BottomNavigationBarItem(
        icon: const Icon(Icons.assignment),
        title: new Text(mainTitles[1]),
        backgroundColor: Colors.black,
      ),
      new BottomNavigationBarItem(
        icon: const Icon(Icons.devices_other),
        title: new Text(mainTitles[2]),
        backgroundColor: Colors.black,
      ),
      new BottomNavigationBarItem(
        icon: const Icon(Icons.person),
        title: new Text(mainTitles[3]),
        backgroundColor: Colors.black,
      ),
    ];
  }


  ///初始化数据
  void initData() {
    indexStack = new IndexedStack(
      children: <Widget>[new TreatmentPage(), new ChargePage(),new GoodsPage(), new SetPage()],
      index: positionIndex,
    );
  }

  ///相当于底部导航栏
  BottomNavigationBar initNavigationBar(){
    return new BottomNavigationBar(
      items: navigationViews.map((BottomNavigationBarItem navigationView) => navigationView).toList(),
      currentIndex: positionIndex,
      type: BottomNavigationBarType.fixed,
      onTap: (index) {
        setState(() {
          positionIndex = index;
        });
      },
    );
  }
}
  1. 使用Offstage实现

Offstage的作用十分简单,通过一个参数来控制child是否显示,所以我们同样可以组合使用Offstage来实现该需求,其实现原理与IndexedStack类似。

class MainDart extends StatefulWidget {

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

class _MainDartState extends State<MainDart> with TickerProviderStateMixin{

  //默认索引
  int positionIndex = 0;
  //底部导航栏
  var mainTitles = ['患者诊疗', '收费','物资', '设置'];
 final bodyList = [new TreatmentPage(), new ChargePage(),new GoodsPage(), new SetPage()];
  List<BottomNavigationBarItem> navigationViews;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: PreferredSize(
        preferredSize:Size.fromHeight(MediaQuery.of(context).size.height * 0.07),
        child:SafeArea(
          top: true,
          child: Offstage(),
        ),
      ),
       body: Stack(
          children: [
            Offstage(
              offstage: positionIndex != 0,
              child: bodyList[0],
            ),
            Offstage(
              offstage: positionIndex != 1,
              child: bodyList[1],
            ),
            Offstage(
              offstage: positionIndex != 2,
              child: bodyList[2],
            ),
             Offstage(
              offstage: positionIndex != 3,
              child: bodyList[3],
            ),
          ],
        ),
      bottomNavigationBar: initNavigationBar(),
    );
  }
  @override
  void initState() {
    super.initState();
    navigationViews = <BottomNavigationBarItem>[
      new BottomNavigationBarItem(
        icon: const Icon(Icons.home),
        title: new Text(mainTitles[0]),
        backgroundColor: Colors.black,
      ),
      new BottomNavigationBarItem(
        icon: const Icon(Icons.assignment),
        title: new Text(mainTitles[1]),
        backgroundColor: Colors.black,
      ),
      new BottomNavigationBarItem(
        icon: const Icon(Icons.devices_other),
        title: new Text(mainTitles[2]),
        backgroundColor: Colors.black,
      ),
      new BottomNavigationBarItem(
        icon: const Icon(Icons.person),
        title: new Text(mainTitles[3]),
        backgroundColor: Colors.black,
      ),
    ];
  }

  ///相当于底部导航栏
  BottomNavigationBar initNavigationBar(){
    return new BottomNavigationBar(
      items: navigationViews.map((BottomNavigationBarItem navigationView) => navigationView).toList(),
      currentIndex: positionIndex,
      type: BottomNavigationBarType.fixed,
      onTap: (index) {
        setState(() {
          positionIndex = index;
        });
      },
    );
  }
}
  1. 前面在底部导航介绍了使用IndexedStack和Offstage两种方式实现保持页面状态,下面我们使用PageView+AutomaticKeepAliveClientMixin重写之前的底部导航。
class MainDart extends StatefulWidget {

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

class _MainDartState extends State<MainDart> with TickerProviderStateMixin{

  //默认索引
  int positionIndex = 0;
  //底部导航栏
  var mainTitles = ['患者诊疗', '收费','物资', '设置'];
 final bodyList = [new TreatmentPage(), new ChargePage(),new GoodsPage(), new SetPage()];
  List<BottomNavigationBarItem> navigationViews;
  final pageController = PageController();
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: PreferredSize(
        preferredSize:Size.fromHeight(MediaQuery.of(context).size.height * 0.07),
        child:SafeArea(
          top: true,
          child: Offstage(),
        ),
      ),
       body:  PageView(
          controller: pageController,
          onPageChanged: onPageChanged,
          children: bodyList,
          physics: NeverScrollableScrollPhysics(), // 禁止滑动
        ),
      bottomNavigationBar: initNavigationBar(),
    );
  }
  void onPageChanged(int index) {
    setState(() {
      positionIndex = index;
    });
  }
  
  @override
  void initState() {
    super.initState();
    navigationViews = <BottomNavigationBarItem>[
      new BottomNavigationBarItem(
        icon: const Icon(Icons.home),
        title: new Text(mainTitles[0]),
        backgroundColor: Colors.black,
      ),
      new BottomNavigationBarItem(
        icon: const Icon(Icons.assignment),
        title: new Text(mainTitles[1]),
        backgroundColor: Colors.black,
      ),
      new BottomNavigationBarItem(
        icon: const Icon(Icons.devices_other),
        title: new Text(mainTitles[2]),
        backgroundColor: Colors.black,
      ),
      new BottomNavigationBarItem(
        icon: const Icon(Icons.person),
        title: new Text(mainTitles[3]),
        backgroundColor: Colors.black,
      ),
    ];
  }

  ///相当于底部导航栏
  BottomNavigationBar initNavigationBar(){
    return new BottomNavigationBar(
      items: navigationViews.map((BottomNavigationBarItem navigationView) => navigationView).toList(),
      currentIndex: positionIndex,
      type: BottomNavigationBarType.fixed,
      onTap: (index) {
       pageController.jumpToPage(index);
      },
    );
  }
}

然后在bodyList的子页State中继承AutomaticKeepAliveClientMixin并重写wantKeepAlive,以GoodsPage.dart为例:

class GoodsPage extends StatefulWidget{

  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    GoodsPageState mstate = new GoodsPageState();
    return mstate;
  }
}

class GoodsPageState extends State<GoodsPage> with AutomaticKeepAliveClientMixin{

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Text('goods');
  }

  @override
  // TODO: implement wantKeepAlive
  bool get wantKeepAlive => true;

}
  1. 使用TabBarView。
    PageView和TabBarView的实现原理类似,TabBarView内部也是用的是PageView。 因此两者的使用方式相同。

  2. 总结:使用IndexedStack和Offstage两种方式实现保持页面状态,但它们的缺点在于第一次加载时便实例化了所有的子页面State。使用TabBarView / PageView+AutomaticKeepAliveClientMixin这种方式既实现了页面状态的保持,又具有类似惰性求值的功能,对于未使用的页面状态不会进行实例化,减小了应用初始化时的开销。实际开发中看情况选择实现方式。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章