Flutter入門進(jìn)階之旅(十六)Scaffold 腳手架

在本欄的前面章節(jié)的學(xué)習(xí)中,我們基本上把Flutter中所有的常用布局、組件包括多頁面跳轉(zhuǎn)路由都介紹過了,細(xì)心的讀者可能會(huì)發(fā)現(xiàn)在前面的課程中我們每次新建一個(gè)Flutter Page的時(shí)候都會(huì)在根布局的build方法中直接return一個(gè)Scaffold然后,再通過配置Scaffold中的相關(guān)屬性來快速的渲染頁面布局:沒錯(cuò)Scaffold的出現(xiàn)就是為了幫助我們實(shí)現(xiàn)基本的 Material Design 布局結(jié)構(gòu),簡化我們頁面開發(fā)工作,我們可以形象的把Scaffold理解為頁面搭建的腳手架

課程學(xué)習(xí)目標(biāo)

了解并掌握Scaffold中提供的快速搭建頁面的腳手架方法
  • appBar: 顯示在界面頂部的一個(gè)菜單導(dǎo)航欄
  • body:頁面中顯示主要內(nèi)容的區(qū)域,可任意指定Widget
  • floatingActionButton: 懸浮按鈕(類似原生Android中的floatingActionButton)
  • drawer、endDrawer:分別用于在左右兩側(cè)邊欄展示抽屜菜單
  • bottomNavigationBar:顯示在底部的導(dǎo)航欄按鈕欄

在Flutter腳手架中給我們提供了上述用于快速構(gòu)建頁面的常用屬性,開發(fā)者可根據(jù)自己的頁面需求,選擇性的引入不同屬性達(dá)到定制出不同UI呈現(xiàn)的目的,關(guān)于Scaffold中的其他屬性,我就不逐個(gè)講解了,下面我結(jié)合代碼跟大家一塊測試下上述常用方法。

Scaffold課程完整效果圖

在這里插入圖片描述

下面我拆分每一部分組件,詳細(xì)對(duì)Scaffold中常用方法做講解說明。

1. appBar

在前面的每一節(jié)課程中我們都可以找到appBar的身影,但是由于之前的課程重點(diǎn)不在appBar上,所以我們并沒有對(duì)appBar展開過多的介紹,就僅僅是作為展示頁面標(biāo)題使用。

我們先來看看Scaffold源碼中對(duì)appBar的解釋說明:一個(gè)展示在Scaffold頂部的APP Bar

  /// An app bar to display at the top of the scaffold.
  final PreferredSizeWidget appBar;

通常我們會(huì)給根據(jù)不同的業(yè)務(wù)場景設(shè)置不同的AppBar,關(guān)于AppBar的構(gòu)造方法大部分屬性讀者都可以根據(jù)屬性名自解釋,我就不貼出來贅述了,下面我舉幾個(gè)常用的例子

1.1 設(shè)置Title
  appBar: AppBar(
          title: Text("Scaffold 腳手架"),
         ),

標(biāo)題居中:

  appBar: AppBar(
          title: Text("Scaffold 腳手架"),
          centerTitle: true,
         ),
默認(rèn)標(biāo)題位置

標(biāo)題居中
1.2設(shè)置左上角返回按鈕

從上面的代碼以及圖片中我們看到Scaffold中默認(rèn)為我們?cè)赼ppBar上指定了一個(gè)返回的箭頭,點(diǎn)擊箭頭返回上一頁,當(dāng)然我們可以通過leading屬性自定義左上角的圖標(biāo),但是當(dāng)我們指定了leading點(diǎn)擊事件就需要我們自己單獨(dú)處理了,也就是說,指定了leading之后,我們就沒辦法在點(diǎn)擊箭頭的時(shí)候結(jié)束當(dāng)前頁,但是我們可以自己實(shí)現(xiàn)這一操作。

 appBar: AppBar(
          leading: GestureDetector(child: Icon(Icons.print),onTap: (){
            Navigator.of(context).pop();
          }),  //添加leading之后需要重寫點(diǎn)擊事件喚
          title: Text("Scaffold 腳手架"),
          centerTitle: true,
         ),
      
自定義左側(cè)按鈕點(diǎn)擊事件
1.3 右側(cè)溢出菜單

在Flutter中我們通過Appbar的 actions屬性設(shè)置菜單項(xiàng),一般重要的菜單選項(xiàng)我們會(huì)直接放在右邊bar上顯示,非重要功能選項(xiàng)我們會(huì)通過PopupMenuButton以三個(gè)小點(diǎn)的形式放進(jìn)折疊菜單里,下面我們結(jié)合源碼看下效果圖,讀者一看便知。

appBar: AppBar(
          leading: GestureDetector(child: Icon(Icons.print),onTap: (){
            Navigator.of(context).pop();
          }),  //添加leading之后需要重寫點(diǎn)擊事件喚起抽屜菜單
          title: Text("Scaffold 腳手架"),
          actions: <Widget>[
            IconButton(icon: Icon(Icons.message), onPressed: () {}),
            IconButton(icon: Icon(Icons.access_alarm), onPressed: () {}),
            PopupMenuButton(
                onSelected: (String value) {
                  print('-----------------$value');
                },
                itemBuilder: (BuildContext context) => [
                      new PopupMenuItem(value: "選項(xiàng)一的內(nèi)容", child: new Text("選項(xiàng)一")),
                      new PopupMenuItem(value: "選項(xiàng)二的內(nèi)容", child: new Text("選項(xiàng)二")),
                      new PopupMenuItem(value: "選項(xiàng)三的內(nèi)容", child: new Text("選項(xiàng)三")),
                    ])
          ],
          ),
右側(cè)菜單
1.4 標(biāo)題欄底部TabBar

在原生Android中我們很熟悉的就是利用TabLayout設(shè)置標(biāo)題欄下方的tab切換效果,在Flutter中我們通過給AppBar的bottom屬性設(shè)置TabBar來完成這一效果。

    bottom: TabBar(
            controller: _tabController,
            tabs: topTabLists
                .map((element) => Tab(
                      text: element,
                    ))
                .toList(),
//            onTap: (index) => {},
          )),

使用TabBar必須傳入controller屬性,我們通過initState()方法初始化_tabController

  @override
  void initState() {
    super.initState();
    //初始化頂部TabController
    _tabController = TabController(length: topTabLists.length, vsync: this);
  }
在這里插入圖片描述

Tab屬性設(shè)置icon圖標(biāo)+文字

 bottom: TabBar(
            controller: _tabController,
            tabs: topTabLists
                .map((element) => Tab(
                      text: element,
                      icon: Icon(Icons.print),
                    ))
                .toList(),
//            onTap: (index) => {},
          )),
          
在這里插入圖片描述

2.body部分

根據(jù)Scaffold頁面布局的上下順序,下面我們講解第二部分body部分。關(guān)于body部分在前面的課程中,我們使用了很多次了,也沒什么需要特別說的地方,這里我就不展開介紹了,body作為一個(gè)頁面的主要視圖部分,可以傳入任意指定的Widget,可現(xiàn)實(shí)在屏幕中央,這里我就不過多贅述了。

  body: Container(
        child: Text("內(nèi)容區(qū)域"),
      ),

3.抽屜菜單

在文章開題的時(shí)候,我提到了抽屜菜單可以通過drawer、endDrawer指定左右兩側(cè)打開的抽屜菜單。下面我們就來看下效果。

左側(cè)抽屜

drawer: MyDrawer(), //MyDrawer詳細(xì)代碼在下面

右側(cè)抽屜

endDrawer: MyDrawer(), //MyDrawer詳細(xì)代碼在下面

左側(cè)抽屜
右側(cè)抽屜

上述抽屜相關(guān)的MyDrawer代碼如下

class MyDrawer extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Drawer(
        child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        Padding(
          padding: const EdgeInsets.only(top: 88.0, bottom: 30.0),
          child: Row(
            children: <Widget>[
              Padding(
                padding: const EdgeInsets.symmetric(horizontal: 16.0),
                child: ClipOval(
                  child: Image.network(
                    "https://avatar.csdn.net/6/0/6/3_xieluoxixi.jpg",
                    width: 60,
                  ),
                ),
              ),
              Text(
                "謝棟",
                style: TextStyle(fontWeight: FontWeight.bold),
              )
            ],
          ),
        ),
        Expanded(
          child: ListView(
            children: <Widget>[
              ListTile(
                leading: const Icon(Icons.settings),
                title: const Text('個(gè)人設(shè)置'),
              ),
              ListTile(
                leading: const Icon(Icons.live_help),
                title: const Text('幫助說明'),
              ),
              ListTile(
                leading: const Icon(Icons.settings),
                title: const Text('個(gè)人設(shè)置'),
              ),
              ListTile(
                leading: const Icon(Icons.live_help),
                title: const Text('幫助說明'),
              ),
            ],
          ),
        )
      ],
    ));
  }
}

4.floatingActionButton

floatingActionButton在前面講解Button章節(jié)中我們講解過,知識(shí)點(diǎn)也比較簡單,就是在頁面布局快速構(gòu)建出一個(gè)懸浮按鈕。

floatingActionButton: FloatingActionButton(
        onPressed: (){
          print("---------");
        },
        child: Icon(Icons.add),
      ),
在這里插入圖片描述

我們可以通過在Scaffold腳手架中指定floatingActionButtonLocation來指定floatingActionButton顯示的位置。


在這里插入圖片描述

各個(gè)位置屬性基本見名知意,我就不逐個(gè)寫效果圖展示了,下面只貼上一個(gè)懸停在底部導(dǎo)航欄上的樣式。

 floatingActionButtonLocation:
          FloatingActionButtonLocation.centerDocked, //設(shè)置FloatingActionButton的位置
    );
在這里插入圖片描述

5.底部導(dǎo)航欄 bottomNavigationBar

bottomNavigationBar的使用場景還比較多,一般我們的多頁面app都會(huì)通過底部的Tab來切換App首頁展示的不同內(nèi)容,在Flutter的Scaffold中為我們提供了快捷用于構(gòu)建底部Tab的方法,我們通過給BottomNavigationBar的Items屬性設(shè)置需要展示的BottomNavigationBarItem數(shù)組即可。

 bottomNavigationBar: BottomNavigationBar(
        //不設(shè)置該屬性多于三個(gè)不顯示顏色
        type: BottomNavigationBarType.fixed,
        items: [
          BottomNavigationBarItem(icon: Icon(Icons.home), title: Text("首頁")),
          BottomNavigationBarItem(icon: Icon(Icons.message), title: Text("消息")),
          BottomNavigationBarItem(
              icon: Icon(Icons.add_a_photo), title: Text("動(dòng)態(tài)")),
          BottomNavigationBarItem(icon: Icon(Icons.person), title: Text("我的"))
        ],
        currentIndex: _currentBottomIndex,
        fixedColor: Colors.blue,
        onTap: (index) => _onBottomTabChange(index),
      ),

在這里插入圖片描述

這里有個(gè)細(xì)節(jié)需要說一下,有讀者私下里問我說設(shè)置了bottomNavigationBar的屬性其中tab為三個(gè)的時(shí)候可以正常顯示,但是多于三個(gè)就變成白色了,不能正常顯示
正常顯示
顯示白色,切換效果不明顯

這里我從源碼角度上給讀者解讀一下。

  /// If [fixedColor] is null then the theme's primary color,
  /// [ThemeData.primaryColor], is used. However if [BottomNavigationBar.type] is
  /// [BottomNavigationBarType.shifting] then [fixedColor] is ignored.
  BottomNavigationBar({
    Key key,
    @required this.items,
    this.onTap,
    this.currentIndex = 0,
    BottomNavigationBarType type,
    this.fixedColor,
    this.iconSize = 24.0,
  }) : assert(items != null),
       assert(items.length >= 2),
       assert(
        items.every((BottomNavigationBarItem item) => item.title != null) == true,
        'Every item must have a non-null title',
       ),
       assert(0 <= currentIndex && currentIndex < items.length),
       assert(iconSize != null),
       type = type ?? (items.length <= 3 ? BottomNavigationBarType.fixed : BottomNavigationBarType.shifting),
       super(key: key);

在這里插入圖片描述

從源碼上的這段標(biāo)紅線的地方,我們可以讀到,當(dāng)BottomNavigationBar中的items數(shù)量小于等于3時(shí),type為BottomNavigationBarType.fixed,大于3則為BottomNavigationBarType.shifting,所以我們只需在代碼中重載type屬性,大于3個(gè)的時(shí)候設(shè)置type值為BottomNavigationBarType.fixed即可。我在一開始的代碼注釋中也解釋了這個(gè)問題。

UI小特效
在實(shí)現(xiàn)底部導(dǎo)航欄時(shí),F(xiàn)lutter還為我們提供了一個(gè)Material組件中的類似‘"鑲嵌"效果,使用BottomAppBar配合FloatingActionButton完成,文字描述可能云里霧里的。

一圖勝千言


懸浮按鈕鑲嵌在Bar上
//與FloatingActionButton配合實(shí)現(xiàn)"打洞"效果
      bottomNavigationBar: BottomAppBar(
        color: Colors.white,
        shape: CircularNotchedRectangle(), // 底部導(dǎo)航欄打一個(gè)圓形的洞
        child: Row(
          children: [
            Tab(text: "首頁", icon: Icon(Icons.home)),
            Tab(text: "消息", icon: Icon(Icons.message)),
            Tab(text: "動(dòng)態(tài)", icon: Icon(Icons.add_a_photo)),
            Tab(text: "我的", icon: Icon(Icons.person)),

          ],
          mainAxisAlignment: MainAxisAlignment.spaceAround, //均分底部導(dǎo)航欄橫向空間
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => _onFabClick,
        child: Icon(Icons.add),
      ),
      floatingActionButtonLocation:
          FloatingActionButtonLocation.centerDocked, //設(shè)置FloatingActionButton的位置
    );
    

雖然我們?cè)谏厦娼柚?code>BottomNavigationBar也實(shí)現(xiàn)了類似的效果,但是前者是直接壓在導(dǎo)航欄上面的,而后者是嵌入進(jìn)去的,效果更逼真。讀者可對(duì)比下方的效果圖。

直接壓在導(dǎo)航欄上
懸浮按鈕鑲嵌在Bar上

限于篇幅問題,本篇博文的完整代碼我就不在此貼出來了,有需要的讀者可以去我的github下載源碼:
ScaffoldPage.dart

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 在上一篇文章中以簡單的方式對(duì)Flutter自己提供的演示進(jìn)行了一個(gè)簡單的分析,當(dāng)然那是遠(yuǎn)遠(yuǎn)不夠。本來打算為大家?guī)?..
    奇葩AnJoiner閱讀 942評(píng)論 0 4
  • Flutter和Dart系列文章和代碼GitHub地址 Flutter一切皆Widget的核心思想, 為我們提供了...
    TitanCoder閱讀 2,678評(píng)論 0 0
  • 1.App結(jié)構(gòu)和導(dǎo)航 Scaffold:Material Design布局結(jié)構(gòu)的基本實(shí)現(xiàn)。此類提供了用于顯示dra...
    慕容小偉閱讀 4,091評(píng)論 0 3
  • 該文已授權(quán)公眾號(hào) 「碼個(gè)蛋」,轉(zhuǎn)載請(qǐng)指明出處 上一篇講完 Flutter 中的一些基本部件,這篇就先填完上篇留下的...
    Kuky_xs閱讀 2,040評(píng)論 6 9
  • Flutter是Google開發(fā)的一套全新的跨平臺(tái)、開源UI框架(本質(zhì)上就是sdk)。 支持iOS、Android...
    HarveyLegend閱讀 8,574評(píng)論 1 42

友情鏈接更多精彩內(nèi)容