flutter 切換tab后保留tab狀態


前言

最近在用flutter寫一個小項目,在寫主頁面(底部導航欄+子頁面)時遇到的一個問題:當點擊底部item切換到另一頁面, 再返回此頁面時會重走它的initState方法(我們一般在initState中發起網絡請求,或者初始化的操作),導致不必要的開銷

根據Tab動態加載頁面

我們先定義兩個頁面PageA和PageB

class PageA extends StatefulWidget {
  _PageAState createState() => _PageAState();
}

class _PageAState extends State<PageA> {
  @override  
  void initState() {
    super.initState();
    print("pageA init state");
  }

  @override  
  Widget build(BuildContext context) {
    return Container(
      color: Colors.orangeAccent,
      child: Center(
        child: Text("page A"),
      ),
    );
  }
}

class PageB extends StatefulWidget {
  _PageBState createState() => _PageBState();
}

class _PageBState extends State<PageB> {
   @override 
  void initState() {
    super.initState();
    print("pageB init state");
  }
  
   @override 
  Widget build(BuildContext context) {
    return Container(
      color: Colors.blueAccent,
      child: Center(
        child: Text("page B"),
      ),
    );
  }
}

定義Tab主頁面

import 'package:flutter/material.dart';

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

class MyApp extends StatefulWidget {
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  int _tabIndex = 0;

  List<Widget> _tabWidget = [
    PageA(),
    PageB()
  ];

   @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'tab demo',
      home: Scaffold(
        bottomNavigationBar: BottomNavigationBar(
          items: _createBottomItems(),
          currentIndex: this._tabIndex,
          onTap: (index) {
            setState(() {
              this._tabIndex = index;
            });
          },
        ),
        body: this._tabWidget.elementAt(this._tabIndex),
      ),
    );
  }
}

// 創建底部導航item
List<BottomNavigationBarItem> _createBottomItems() {
  return [
    BottomNavigationBarItem(
      icon: Icon(Icons.home),
      title: Text("首頁")
    ),
    BottomNavigationBarItem(
      icon: Icon(Icons.insert_emoticon),
      title: Text("我的")
    )
  ];

運行后發現,每次切換tab都會調用initState,這顯然不符合我們的正常的需求,有下面兩種解決方式

IndexedStack

IndexedStack可以控制子元素的顯示和隱藏,並且會緩存所有的元素,不會每次都重新創建子元素

  @override
Widget build(BuildContext context) {
  return MaterialApp(
    title: 'tab demo',
    home: Scaffold(
      bottomNavigationBar: BottomNavigationBar(
        items: _createBottomItems(),
        currentIndex: this._tabIndex,
        onTap: (index) {
          setState(() {
            this._tabIndex = index;
          });
        },
      ),
      body: IndexedStack(
        children: this._tabWidget,
        index: this._tabIndex,
      )
    ),
  );
}

運行后發現還是有個問題,IndexedStack在初始化的時候會初始化所有的子元素,pageA和pageB的initState會同時調用,這明顯還是不符合我們的需求

正確來說應該是切換到具體頁面的時候才進行初始化,而不是一開始就加載所有的頁面的數據,避免資源浪費

PageView + AutomaticKeepAliveClientMixin

使用PageView支持多個view切換,並且不會一次加載完所有的頁面

PageController _pageController;
  
   @override 
  void initState() {
    super.initState();
    this._pageController =PageController(initialPage: this._tabIndex, keepPage: true);
  }

  @override  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'tab demo',
      home: Scaffold(
        bottomNavigationBar: BottomNavigationBar(
          items: _createBottomItems(),
          currentIndex: this._tabIndex,
          onTap: (index) {
            setState(() {
              this._tabIndex = index;
              _pageController.jumpToPage(index);
            });
          },
        ),
        body: PageView(
          children: this._tabWidget,
          controller: _pageController,
        ),
      ),
    );
  }
}

使用PageView可以正常切換,但是每次切換Tab的時候還是會重復調用initState,我們還需要在子頁面實現AutomaticKeepAliveClientMixin

class _PageAState extends State<PageA> with AutomaticKeepAliveClientMixin {
  @override  
  bool get wantKeepAlive => true;
  
  ...
}
class _PageBState extends State<PageB> with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  ...
}

實現了AutomaticKeepAliveClientMixin就不會每次切換Tab都調用initState了,這也是google推薦的方式

最后發現PageView可以左右滑動切換,這個可以通過設置physics為NeverScrollableScrollPhysics()來禁止滑動

PageView(
  children: this._tabWidget,
  controller: _pageController,
  physics: NeverScrollableScrollPhysics(),
)

參考來源:https://blog.bombox.org/2019-02-23/flutter-tab-keep-alive/#more


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM