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這種方式既實現了頁面狀態的保持,又具有類似惰性求值的功能,對於未使用的頁面狀態不會進行實例化,減小了應用初始化時的開銷。實際開發中看情況選擇實現方式。

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