Flutter實戰(zhàn) Scaffold、TabBar、底部導(dǎo)航

2021-03-08 10:41 更新

Material 組件庫提供了豐富多樣的組件,本節(jié)介紹一些常用的組件,其余的讀者可以自行查看文檔或 Flutter Gallery 中 Material 組件部分的示例。

Flutter Gallery 是 Flutter 官方提供的 Flutter Demo,源碼位于 flutter 源碼中的 examples 目錄下,筆者強烈建議用戶將 Flutter Gallery 示例跑起來,它是一個很全面的 Flutter 示例應(yīng)用,是非常好的參考 Demo,也是筆者學(xué)習 Flutter 的第一手資料。

#5.6.1 Scaffold

一個完整的路由頁可能會包含導(dǎo)航欄、抽屜菜單(Drawer)以及底部Tab導(dǎo)航菜單等。如果每個路由頁面都需要開發(fā)者自己手動去實現(xiàn)這些,這會是一件非常麻煩且無聊的事。幸運的是,F(xiàn)lutter Material組件庫提供了一些現(xiàn)成的組件來減少我們的開發(fā)任務(wù)。Scaffold是一個路由頁的骨架,我們使用它可以很容易地拼裝出一個完整的頁面。

#示例

我們實現(xiàn)一個頁面,它包含:

  1. 一個導(dǎo)航欄
  2. 導(dǎo)航欄右邊有一個分享按鈕
  3. 有一個抽屜菜單
  4. 有一個底部導(dǎo)航
  5. 右下角有一個懸浮的動作按鈕

最終效果如圖5-18、圖5-19所示:

圖5-18 圖5-19

實現(xiàn)代碼如下:

class ScaffoldRoute extends StatefulWidget {
  @override
  _ScaffoldRouteState createState() => _ScaffoldRouteState();
}


class _ScaffoldRouteState extends State<ScaffoldRoute> {
  int _selectedIndex = 1;


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar( //導(dǎo)航欄
        title: Text("App Name"), 
        actions: <Widget>[ //導(dǎo)航欄右側(cè)菜單
          IconButton(icon: Icon(Icons.share), onPressed: () {}),
        ],
      ),
      drawer: new MyDrawer(), //抽屜
      bottomNavigationBar: BottomNavigationBar( // 底部導(dǎo)航
        items: <BottomNavigationBarItem>[
          BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('Home')),
          BottomNavigationBarItem(icon: Icon(Icons.business), title: Text('Business')),
          BottomNavigationBarItem(icon: Icon(Icons.school), title: Text('School')),
        ],
        currentIndex: _selectedIndex,
        fixedColor: Colors.blue,
        onTap: _onItemTapped,
      ),
      floatingActionButton: FloatingActionButton( //懸浮按鈕
          child: Icon(Icons.add),
          onPressed:_onAdd
      ),
    );
  }
  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }
  void _onAdd(){
  }
}

上面代碼中我們用到了如下組件:

組件名稱 解釋
AppBar 一個導(dǎo)航欄骨架
MyDrawer 抽屜菜單
BottomNavigationBar 底部導(dǎo)航欄
FloatingActionButton 漂浮按鈕

下面我們來分別介紹一下它們。

#5.6.2 AppBar

AppBar是一個 Material 風格的導(dǎo)航欄,通過它可以設(shè)置導(dǎo)航欄標題、導(dǎo)航欄菜單、導(dǎo)航欄底部的Tab標題等。下面我們看看 AppBar 的定義:

AppBar({
  Key key,
  this.leading, //導(dǎo)航欄最左側(cè)Widget,常見為抽屜菜單按鈕或返回按鈕。
  this.automaticallyImplyLeading = true, //如果leading為null,是否自動實現(xiàn)默認的leading按鈕
  this.title,// 頁面標題
  this.actions, // 導(dǎo)航欄右側(cè)菜單
  this.bottom, // 導(dǎo)航欄底部菜單,通常為Tab按鈕組
  this.elevation = 4.0, // 導(dǎo)航欄陰影
  this.centerTitle, //標題是否居中 
  this.backgroundColor,
  ...   //其它屬性見源碼注釋
})

如果給Scaffold添加了抽屜菜單,默認情況下Scaffold會自動將AppBarleading設(shè)置為菜單按鈕(如上面截圖所示),點擊它便可打開抽屜菜單。如果我們想自定義菜單圖標,可以手動來設(shè)置leading,如:

Scaffold(
  appBar: AppBar(
    title: Text("App Name"),
    leading: Builder(builder: (context) {
      return IconButton(
        icon: Icon(Icons.dashboard, color: Colors.white), //自定義圖標
        onPressed: () {
          // 打開抽屜菜單  
          Scaffold.of(context).openDrawer(); 
        },
      );
    }),
    ...  
  )  

代碼運行效果如圖5-20所示:

可以看到左側(cè)菜單已經(jīng)替換成功。

代碼中打開抽屜菜單的方法在ScaffoldState中,通過Scaffold.of(context)可以獲取父級最近的Scaffold 組件的State對象。

#TabBar

下面我們通過“bottom”屬性來添加一個導(dǎo)航欄底部 Tab 按鈕組,將要實現(xiàn)的效果如圖5-21所示:

圖5-21

Material 組件庫中提供了一個TabBar組件,它可以快速生成Tab菜單,下面是上圖對應(yīng)的源碼:

class _ScaffoldRouteState extends State<ScaffoldRoute>
    with SingleTickerProviderStateMixin {


  TabController _tabController; //需要定義一個Controller
  List tabs = ["新聞", "歷史", "圖片"];


  @override
  void initState() {
    super.initState();
    // 創(chuàng)建Controller  
    _tabController = TabController(length: tabs.length, vsync: this);
  }


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        ... //省略無關(guān)代碼
        bottom: TabBar(   //生成Tab菜單
          controller: _tabController,
          tabs: tabs.map((e) => Tab(text: e)).toList()
        ),
      ),
      ... //省略無關(guān)代碼


  }

上面代碼首先創(chuàng)建了一個TabController ,它是用于控制/監(jiān)聽Tab菜單切換的。接下來通過 TabBar 生成了一個底部菜單欄,TabBartabs屬性接受一個 Widget 數(shù)組,表示每一個 Tab 子菜單,我們可以自定義,也可以像示例中一樣直接使用Tab 組件,它是 Material 組件庫提供的 Material 風格的 Tab 菜單。

Tab組件有三個可選參數(shù),除了可以指定文字外,還可以指定Tab菜單圖標,或者直接自定義組件樣式。Tab組件定義如下:

Tab({
  Key key,
  this.text, // 菜單文本
  this.icon, // 菜單圖標
  this.child, // 自定義組件樣式
})

開發(fā)者可以根據(jù)實際需求來定制。

#TabBarView

通過TabBar我們只能生成一個靜態(tài)的菜單,真正的 Tab 頁還沒有實現(xiàn)。由于Tab菜單和 Tab 頁的切換需要同步,我們需要通過TabController去監(jiān)聽 Tab 菜單的切換去切換 Tab 頁,代碼如:

_tabController.addListener((){  
  switch(_tabController.index){
    case 1: ...;
    case 2: ... ;   
  }
});

如果我們 Tab 頁可以滑動切換的話,還需要在滑動過程中更新 TabBar 指示器的偏移!顯然,要手動處理這些是很麻煩的,為此,Material 庫提供了一個TabBarView組件,通過它不僅可以輕松的實現(xiàn) Tab 頁,而且可以非常容易的配合 TabBar 來實現(xiàn)同步切換和滑動狀態(tài)同步,示例如下:

Scaffold(
  appBar: AppBar(
    ... //省略無關(guān)代碼
    bottom: TabBar(
      controller: _tabController,
      tabs: tabs.map((e) => Tab(text: e)).toList()),
  ),
  drawer: new MyDrawer(),
  body: TabBarView(
    controller: _tabController,
    children: tabs.map((e) { //創(chuàng)建3個Tab頁
      return Container(
        alignment: Alignment.center,
        child: Text(e, textScaleFactor: 5),
      );
    }).toList(),
  ),
  ... // 省略無關(guān)代碼  
)    

運行后效果如圖5-22所示:

圖5-22

現(xiàn)在,無論是點擊導(dǎo)航欄 Tab 菜單還是在頁面上左右滑動,Tab 頁面都會切換,并且 Tab 菜單的狀態(tài)和 Tab 頁面始終保持同步!那它們是如何實現(xiàn)同步的呢?細心的讀者可能已經(jīng)發(fā)現(xiàn),上例中TabBarTabBarViewcontroller是同一個!正是如此,TabBarTabBarView正是通過同一個controller來實現(xiàn)菜單切換和滑動狀態(tài)同步的,有關(guān)TabController的詳細信息,我們不在本書做過多介紹,使用時讀者直接查看 SDK 即可。

另外,Material 組件庫也提供了一個PageView 組件,它和TabBarView功能相似,讀者可以自行了解一下。

#5.6.3 抽屜菜單Drawer

ScaffolddrawerendDrawer屬性可以分別接受一個 Widget 來作為頁面的左、右抽屜菜單。如果開發(fā)者提供了抽屜菜單,那么當用戶手指從屏幕左(或右)側(cè)向里滑動時便可打開抽屜菜單。本節(jié)開始部分的示例中實現(xiàn)了一個左抽屜菜單MyDrawer,它的源碼如下:

class MyDrawer extends StatelessWidget {
  const MyDrawer({
    Key key,
  }) : super(key: key);


  @override
  Widget build(BuildContext context) {
    return Drawer(
      child: MediaQuery.removePadding(
        context: context,
        //移除抽屜菜單頂部默認留白
        removeTop: true,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.only(top: 38.0),
              child: Row(
                children: <Widget>[
                  Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 16.0),
                    child: ClipOval(
                      child: Image.asset(
                        "imgs/avatar.png",
                        width: 80,
                      ),
                    ),
                  ),
                  Text(
                    "Wendux",
                    style: TextStyle(fontWeight: FontWeight.bold),
                  )
                ],
              ),
            ),
            Expanded(
              child: ListView(
                children: <Widget>[
                  ListTile(
                    leading: const Icon(Icons.add),
                    title: const Text('Add account'),
                  ),
                  ListTile(
                    leading: const Icon(Icons.settings),
                    title: const Text('Manage accounts'),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

抽屜菜單通常將Drawer組件作為根節(jié)點,它實現(xiàn)了 Material 風格的菜單面板,MediaQuery.removePadding可以移除 Drawer 默認的一些留白(比如 Drawer 默認頂部會留和手機狀態(tài)欄等高的留白),讀者可以嘗試傳遞不同的參數(shù)來看看實際效果。抽屜菜單頁由頂部和底部組成,頂部由用戶頭像和昵稱組成,底部是一個菜單列表,用 ListView 實現(xiàn),關(guān)于 ListView 我們將在后面“可滾動組件”一節(jié)詳細介紹。

#5.6.4 FloatingActionButton

FloatingActionButton是 Material 設(shè)計規(guī)范中的一種特殊 Button,通常懸浮在頁面的某一個位置作為某種常用動作的快捷入口,如本節(jié)示例中頁面右下角的"?"號按鈕。我們可以通過ScaffoldfloatingActionButton屬性來設(shè)置一個FloatingActionButton,同時通過floatingActionButtonLocation屬性來指定其在頁面中懸浮的位置,這個比較簡單,不再贅述。

#5.6.5 底部Tab導(dǎo)航欄

我們可以通過ScaffoldbottomNavigationBar屬性來設(shè)置底部導(dǎo)航,如本節(jié)開始示例所示,我們通過 Material 組件庫提供的BottomNavigationBarBottomNavigationBarItem兩種組件來實現(xiàn) Material 風格的底部導(dǎo)航欄??梢钥吹缴厦娴膶崿F(xiàn)代碼非常簡單,所以不再贅述,但是如果我們想實現(xiàn)如圖5-23所示效果的底部導(dǎo)航欄應(yīng)該怎么做呢?

圖5-23

Material組件庫中提供了一個BottomAppBar 組件,它可以和FloatingActionButton配合實現(xiàn)這種“打洞”效果,源碼如下:

bottomNavigationBar: BottomAppBar(
  color: Colors.white,
  shape: CircularNotchedRectangle(), // 底部導(dǎo)航欄打一個圓形的洞
  child: Row(
    children: [
      IconButton(icon: Icon(Icons.home)),
      SizedBox(), //中間位置空出
      IconButton(icon: Icon(Icons.business)),
    ],
    mainAxisAlignment: MainAxisAlignment.spaceAround, //均分底部導(dǎo)航欄橫向空間
  ),
)

可以看到,上面代碼中沒有控制打洞位置的屬性,實際上,打洞的位置取決于FloatingActionButton的位置,上面FloatingActionButton的位置為:

floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,

所以打洞位置在底部導(dǎo)航欄的正中間。

BottomAppBarshape屬性決定洞的外形,CircularNotchedRectangle實現(xiàn)了一個圓形的外形,我們也可以自定義外形,比如,F(xiàn)lutter Gallery 示例中就有一個“鉆石”形狀的示例,讀者感興趣可以自行查看。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號