iT邦幫忙

2023 iThome 鐵人賽

DAY 14
1
Mobile Development

Flutter 從零到實戰 - 30 天の學習筆記系列 第 14

[Day 14] 實戰新聞 APP - 導覽列、主題與字體設定

  • 分享至 

  • xImage
  •  

今天我們將實作當使用者成功登入後看到的頁面。我們會實作一個底部導覽列進行頁面的切換。效果如下:

https://ithelp.ithome.com.tw/upload/images/20230929/20135082xQoxXeFlwU.png

讓我們開始吧!

底部導覽列

Cupertino 提供了一個好用的底部導覽列的元件 CupertinoTabScaffold,它實作了 iOS 風格的底部導覽列設計。

在引用之前可以先仔細想一下頁面之間的階層關係,我們的新聞 APP 即將製作四個頁面分別為:

  1. 主頁 - 顯示熱門、焦點新聞
  2. 探索 - 探索各類新聞類別、新聞來源
  3. 搜尋 - 搜尋頁面
  4. 個人檔案 - 個人帳號相關設定

這四個頁面之間並沒有明顯的階層關係,因此使用底部導覽列的設計相當合理。可以看作四個為各自不同的分頁。也因此,在 CupertinoTabScaffold 中這四者的 Navigator 是各自分開的,也就是有四個不同的 Stack 來管理路由的狀態。

同樣的在使用 widget 前,我們先來看看類別定義:

CupertinoTabScaffold({
    super.key,
    required this.tabBar,         // 顯示在底部的導覽列
    required this.tabBuilder,     // 根據導覽列的狀態建構上方的頁面內容
    this.controller,
    this.backgroundColor,
    this.resizeToAvoidBottomInset = true,
    this.restorationId,
  })

從類別定義中,我們可以知道我們至少需要提供 tabBartabBuilder。請在 screens 資料夾底部新增一個 tab_layout.dart 的檔案,並參考下列程式碼:

// 記得要把 main.dart 的 home 參數改為顯示 Tablayout()
class TabLayout extends StatelessWidget {
  const TabLayout({super.key});

  @override
  Widget build(BuildContext context) {
    return CupertinoTabScaffold(
        tabBar: CupertinoTabBar(items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(
              icon: Icon(CupertinoIcons.house_fill, size: 24), label: '主頁'),
          BottomNavigationBarItem(
              icon: Icon(CupertinoIcons.square_grid_2x2_fill, size: 24),
              label: '探索'),
          BottomNavigationBarItem(
              icon: Icon(CupertinoIcons.search, size: 24), label: '搜尋'),
          BottomNavigationBarItem(
              icon: Icon(CupertinoIcons.person_crop_circle, size: 24),
              label: '個人檔案'),
        ]),
        tabBuilder: (BuildContext context, int index) => 
            CupertinoPageScaffold(
              child: Center(
                child: Text('Tab $index'),
              ),
            ));
  }
}

上述程式碼我們在 tabBar 參數串接了由 BottomnavigationItem 組成的 List,並且每個元素皆有給定指定的 Icon 與分頁名稱。tabBuilder 則是顯示當前的 tab 的索引值。

圖標使用的是 flutter 預載的 cupertino_icons 套件所提供,引用方法可參考上方程式碼。也可到這個網站檢視所有的 Icons,並自行任意的替換。

這時候你應該會發現底部的導覽列(藍色)好像顏色跟設計稿的顏色不一樣(紅色)

主題樣式設定

當我們在設計一款應用或是網頁時,通常都會決定好一個顏色作為主色 (primary color),原因無他,就是為了讓整體更具一體性。

因此我們有必要在一開始就設定好應用程式的主題。在 Cupertino 中有提供 CupertinoThemeData 的類別專門用於進行這類的設定,透過類別定義了來看看我們能設定些什麼屬性:

const CupertinoThemeData({
  Brightness? brightness,   // 亮色與暗色模式
  Color? primaryColor,      // 設定主色
  Color? primaryContrastingColor,  // 與主色對比的顏色
  CupertinoTextThemeData? textTheme, // 定義文本的樣式
  Color? barBackgroundColor,  // 導航列的顏色
  Color? scaffoldBackgroundColor,  // 骨架的背景顏色
  bool? applyThemeToAll,  // 是否自動套用至所有 Cupertino 的 widget
})

因此我們可以從此部分針對文字、主色來進行設定。請打開 main.dart 並在 CupertinoApp 中添加程式碼,我們將為整個應用程式進行主題設定:

CupertinoApp(
  // 上方省略
  theme: CupertinoThemeData(
    // 文字相關的主題設定
    textTheme: CupertinoTextThemeData(
      // 大標題樣式設定
      navLargeTitleTextStyle: TextStyle(
          fontSize: 34,
          fontWeight: FontWeight.w700,
          color: CupertinoColors.black,
      ),
      // 一般文字樣式設定
      textStyle: TextStyle(
        fontSize: 16,
        fontWeight: FontWeight.w400,
        color: CupertinoColors.black,
      ),
    ),
    // 主色
    primaryColor: Color.fromRGBO(255, 30, 84, 1),
    // 導覽列背景顏色
    barBackgroundColor: CupertinoColors.white,
  ),
);

加入之後便會套用設定至當前的應用程式上,此時可以發現底部的導覽列的顏色已經套用了我們設定的 primaryColor

加入頂部工具列

目前我們離頁面還尚缺了頂部的工具列,其用於顯示當前頁面的標題、操作按鈕以及返回上頁的按鈕。

我們來仔細觀察一下原生 iOS 的 APP 的頂部工具列行為

https://i.imgur.com/D7T6x8S.gif

  1. 當路由 push 至下頁時,原先的標題經由動畫變為返回上頁的按鈕
  2. 滑動頁面時,縮小標題並置中顯示

在 Cupertino 提供了 CupertinoSliverNavigationBar 實現了相應的行為。我們將其加入至 tab_layout.dart 的程式碼中。

// 僅顯示 tabBuilder 的內容
tabBuilder: (BuildContext context, int index) => 
  CupertinoPageScaffold(
    child: CustomScrollView(slivers: <Widget>[
        CupertinoSliverNavigationBar(
          largeTitle: Text(tabTitle[index]),
          backgroundColor: CupertinoColors.white),
]))

我們來一一講解上述程式碼的內容:

  • 首先前面說過 tabBuilder 用於建構當前 tab 的頁面。
  • 建構每個頁面的骨架即為 CupertinoPageScaffold widget
  • 由於在 iOS 頂部工具列具備滑動時縮小並置中的特性,因此將骨架內容宣告為可滑動的 CustomScrollView
  • 置入 CupertinoSliverNavigationBar 並設定 tab 索引值對應至正確的標題。

試著操作看看當前的頁面吧!原則上可以根據不同的 tab 顯示不同的標題,並且也可以達成滑動時將當前標題縮小置中的效果。

看起來目前都相當的棒。

不過因為我個人喜歡較圓的字體XDD 所以我想在本篇的最後面來介紹如何引用外部字體。

引用字體

在前面的章節我們有建立 assets 這個資料夾並用於放置圖片、字體檔案。前面章節也有介紹過要如何引入圖片並呈現於應用程式中了,其實字體也大同小異。

在選擇字體時,我們先把握一個原則。當我們在看網頁或是報章雜誌時,通常會使用字體大小以及粗細程度來階層化。

  • 大標題的字體最粗最大

  • 副標題字體稍小也較大標題細

  • 一般內文也可以使用粗體來表達重要程度

雖然這並非挑選字體的鐵則,不過請挑選至少有 5 種粗細程度的字體,在顯示效果上才能更加的豐富。

你可以透過 Google Fonts 找到你喜歡的字體,並透過 google_fonts 這個套件進行引入。關於套件使用方式可參考該套件的文件,我們就不花篇幅來講解。

這裡我從網路上找到了一款可免費商用的字體「源泉源體」,該款字體提供了 6 種不同的粗細程度樣式,因此我將以如何引入該字體來進行介紹。

  1. 下載字體:請前往 https://github.com/ButTaiwan/gensen-font,並點選 「Download ZIP」 按鈕下載字體的壓縮檔

    https://ithelp.ithome.com.tw/upload/images/20230929/20135082I97mwPCvdO.png

  2. 解壓縮後將 ttc 當中的 6 個字體檔移至 assets/fonts 資料夾下

  3. 打開 pubspsec.yml 檔案,並輸入以下文字內容

    fonts:
        - family: GenSenRounded
          fonts:
            - asset: assets/fonts/GenSenRounded-EL.ttc
              weight: 200
            - asset: assets/fonts/GenSenRounded-L.ttc
              weight: 300
            - asset: assets/fonts/GenSenRounded-R.ttc
              weight: 400
            - asset: assets/fonts/GenSenRounded-M.ttc
              weight: 500
            - asset: assets/fonts/GenSenRounded-B.ttc
              weight: 700
            - asset: assets/fonts/GenSenRounded-H.ttc
              weight: 900
    

    我們針對這個字體分別使不同的 weight 對應至不同的字體樣式檔

  4. 實際使用:請打開 main.dart 中方才新增的 textTheme 程式碼,加入字體

    textTheme: CupertinoTextThemeData(
        navLargeTitleTextStyle: TextStyle(
            fontSize: 34,
            fontWeight: FontWeight.w700, // 查上方的表,套用 GenSenRounded-B.ttc
            fontFamily: 'GenSenRounded', // 套用我們方才引用的字體
            color: CupertinoColors.black,
            letterSpacing: 1.05),
        textStyle: TextStyle(
          fontSize: 16,
          fontWeight: FontWeight.w400, // 查上方的表,套用 GenSenRounded-R.ttc
          fontFamily: 'GenSenRounded', // 套用我們方才引用的字體
          color: CupertinoColors.black,
        ),
      ),
    
  5. 引用成功拉

    https://ithelp.ithome.com.tw/upload/images/20230929/20135082bRSmHsDFlM.png

今日總結

今天我們根據應用程式的需求建構了底部的導航列,也加入了頂部的工具列讓整個程式碼的外框更加完整。同時我們也套用了主題、字形樣式以及引入了網路上的開源字體。

我想我們可以認同 Flutter 所提供的這套 Cupertino 設計工具真的很強大,不僅在動畫、效果上都非常接近原生的 iOS 應用程式。不說我還以為是用 SwiftUI 開發出來的介面呢 XDD

明天開始會開始開發「探索頁面」的部分,我們將會介紹兩個在 Flutter 中用來顯示列表的重要組件 ListViewGridView,我們明天繼續努力!

今天的參考程式碼:https://github.com/ChungHanLin/micro_news_tutorial/tree/day14/micro_news_app


上一篇
[Day 13] 實戰新聞 APP - Google Sign In 實作
下一篇
[Day 15] 實戰新聞 APP - 滾動式widget (ListView 、GridView與 Sliver widget)
系列文
Flutter 從零到實戰 - 30 天の學習筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言