iT邦幫忙

2024 iThome 鐵人賽

DAY 15
0

Scaffold 組件

前面我們已經使用 Scaffold 組件很多次了,但對它並沒有過多著墨,其實 Scaffold 還有很多隱藏的功能可以協助我們建置一個更實用的應用程式。這個段落我們將探討一些常用的功能。

Drawer

其中一個重要的功能就是 drawer 參數提供的,它可以加入一個側邊欄,可以展開和收合藏起來。你應該在很多應用都看過這個效果,看起來好像很難實作。但在 Flutter 這個功能非常簡單。

首先我們需要定義一個 GlobalKey。讓我們可以直接存取 Scaffold

final _scaffoldKey = GlobalKey<ScaffoldState>();

然後將 Key 傳入 Scaffold

Scaffold(
	key: _scaffoldKey,
  ...
)

下一步,我們需要定義我們的 drawer

drawer: Drawer(
	child: Column(
  	children: [
      IconButton(
      	onPressed: () => _scaffoldKey.currentState!.closeDrawer(),
        icon: Icon(Icons.close)
      ),
      TextButton(
      	child: Text("按鈕"),
        onPressed: () {},
      )
    ]
  ),
)

上面是設定 Scaffolddrawer 參數,我們使用 Drawer 組件,但這不是必須的,只是因為這個組件相對容易使用而且已經具備一樣合適的樣式。內部的話我們使用 Column 來組織按鈕,這些組件的用法我們已經了解了。其中比較陌生的應該是

onPressed: () => _scaffoldKey.currentState!.closeDrawer(),

當按鈕點擊的時候,利用 _scaffoldKey 讀取 Scaffold 的狀態,然後呼叫 closeDrawer()

不過,我們還需要一種方式來開啟這個側邊欄,我們可以在主頁加入按鈕

ElevatedButton(
  onPressed: () {
    _scaffoldKey.currentState!.openDrawer();
  },
  child: const Text("打開"),
),

如你所見,開啟和關閉的操作非常類似,都需要取得 Scaffold 的狀態,然後呼叫對應的方法。若需要進一步在進入畫面立即開啟可以使用 DrawerController 控制初始值或在 initState 生命週期方法中呼叫。

這裡,也呼應表單一節提到的 controller 的方式。我們可以預期許多組件應該存在類似的設計和使用方式。

Snackbar

drawer 類似的還有 snackbar 功能。在之前的章節有稍微介紹。這是一種簡易的訊息通知,從螢幕下方向上滑出呈現,用於向使用者提供一些訊息反饋,例如操作的結果。

雖然一樣是滑出組件,但兩者的實作差異非常大如下範例:

ElevatedButton(
	onPressed: () {
    ScaffoldMessenger.of(context).showSnackBar(
    	SnackBar(
      	content: Text('彈出訊息'),
      ),
    );
  },
  child: Text('顯示'),
)

drawer 不同,我們不用通過 Scaffold 的 Key 存取狀態並呼叫對應方法,而是通過 ScaffoldMessenger.of 找到對應的 InheritedWidget ,它會負責處理建立和顯示 Snackbar。

ScaffoldMessenger.of(context)

除了這種方式之外,在無法取得 BuildContext 的情況下還可以使用 GlobalKey 的方式,步驟如下

建立 GlobalKey<ScaffoldMessengerState>物件:

final GlobalKey<ScaffoldMessengerState> snackbarKey = GlobalKey<ScaffoldMessengerState>();

GlobalKeyMaterialAppScaffoldMessenger 關聯:

MaterialApp(
  scaffoldMessengerKey: snackbarKey,
  home: Scaffold(
    body: HomePage(),
  ),
);

或者與 ScaffoldMessenger 組件關聯,並包住 Scaffold

ScaffoldMessenger(
  key: snackbarKey,
  child: Scaffold(
    body: HomePage(),
  ),
);

在需要顯示 SnackBar 的地方,使用 GlobalKey 存取 ScaffoldMessengerState 並呼叫 showSnackBar 方法

void showSnackbar(String message) {
  snackbarKey.currentState?.showSnackBar(
    SnackBar(
      content: Text(message),
    ),
  );
}

現在,我們可以在任何地方呼叫 showSnackbar 來顯示 SnackBar,而不需要通過 BuildContext

補充:在 Flutter 1.22 版本之前並沒有 ScaffoldMessenger 而是使用 Scaffold.of(context).showSnackBar() 來顯示 SnackBar ,此時的SnackBar 是與當前的 Scaffold 關聯的。如果我們在一個頁面上顯示了 SnackBar,然後導向另一個頁面,SnackBar 會自動消失。因為當前頁面的 Scaffold 被替換為新頁面的 Scaffold,而原來的 SnackBar 是與舊的 Scaffold 關聯的,所以它會消失。為了解決這個問題, ScaffoldMessenger 因應而生。

建立列表

前面章節我們簡單的介紹了 ListView,知道了這個組件可用來呈現列表和支援捲動的功能。但其實它也有一些互相配合的組件 ListTileListTile 簡單的建立一個結構用來放置顯示於列表中的組件,例如 Icon,文字或圖片。你當然可以使用 Container 等組件模仿出 ListTile 的結構,但結構的抽象和相關佈局的程式簡化可以讓你跟專注在應用程式希望提供的內容。下面為 ListTile 的範例:

ListTile(
	title: Text('項目'),
  subtitle: Text('簡短說明'),
  leading: Icon(Icons.location_on),
  onTap: () {},
  trailing: IconButton(
  	icon: Icon(Icons.thumb_up),
    onPressed: () {},
  ),
)
  • leading 組件左邊,一般用來顯示列表項目的 Icon 或圖片。
  • title 粗體較大的文字顯示項目的主要文字。
  • subtitle 在粗體文字下方較小的文字,顯示一些簡單的補充說明。
  • trailingleading 相反,出現在右邊區塊,通常用來顯示一些對應操作的按鈕。

此外,ListTile 還有其他功能:

  • 可以作為按鈕並支援水波紋特效,hover,focus 自訂顏色等。
  • 可以設定由右至左對調 leadingtrailing
  • 背景色,文字顏色也可設定。
  • 若需要更多空間,可以設定例如 isThreeLine 增加 subtitle 的空間擴展至三行。

總體來說 ListTile 非常方便,可以在我們第一次排版的時候節省很多時間。

理解 Flutter 中的圖片類型

圖片有很多類型,了解什麼情況該使用什麼格式有時並不是那麼單純容易。這裡無法無限展開討論,但希望提供一個概覽,讓你可以比較明智的決定該使用何種類型圖片。

預設支援的類型

Flutter 預設就支援很多圖片格式:

  • JPEG:Joint Photographic Experts Group 這是一種非常常見的格式。使用有損壓縮,意味著縮小檔案大小會失去圖片細節。是最廣泛使用的格式。但不適合呈現線條或圖示,因為壓縮過程可能會產生雜訊
  • PNG:Portable Network Graphics 不像 JPEG,PNG 的壓縮不會失真,意思是大檔可以被壓縮的較小且不會遺失資訊。PNG 支援 24 位元的 RGB 或 32 位元的 RGBA 色彩,可以包含超過 256 種顏色,每個像素可以有獨特的顏色值。然而它並非為專業輸出印刷而設計的。PNG 是 GIF 的開源替代方案。
  • GIF 和動畫 GIF: Graphics Interchange Format ,每個 GIF 都定義了自己的 256 色調色板,對解析度低的圖片很實用,但對於細節的呈現不適合。GIF 還可以製作簡單的動畫。
  • WebP 和動畫 WebP:WebP 是一種現代的圖像格式,主要為網路上的圖片,支援優秀的無損和有損壓縮。和 PNG 相比無損圖片小 26%,是目前推薦的格式
  • BMP 和 WBMP:Bitmap 是相對單純的格式,通常檔案大小比較大,不是應用程式或網站推薦的格式,但 Flutter 還是支援。

這些是常見的圖片格式,在它們之間選擇應該是相對容易,除此之外還有一些值得深入理解的格式。

SVG

Scalable Vector Graphics 使用 XML 來定義向量圖片。一個好處是無論把圖片放大多少多不會失真。另外 XML 可以無損壓縮。但 SVG 是一個相對複雜的格式,因為其支援 CSS 功能。

**Flutter 預設不支援 SVG 因此使用 SVG 可能會是挑戰。**雖然預設不支援,但你可以使用套件:

  • flutter_svg 這是一個簡易使用的套件,適用大多數 SVG,載入 SVG 和其他預設圖片格式很接近,例如 SvgPicture.asset(name) ,但是這個套件並不完全支援所有的 SVG 規範和 CSS 樣式。 SVG 的樣式應使用屬性來設定,而不是 CSS 的 style 屬性。不要使用 style="color: blue;" ,應該使用 fill="blue" 來設定顏色。這樣可以確保 SVG 能夠正確呈現。
  • jovial_svg 強大的 SVG 套件,支援 CSS 因此可以支援大多數 SVG,但使用上比較複雜。

值得注意的是兩個套件都支援將 SVG 預先編譯為等價二進制檔案的功能,以便更快速的處理圖片。這表示 SVG 在一些需要速度的應用上不是最佳選擇。預先編譯後的檔案可以像內建格式一樣在程式中使用。

Lottie 檔案

Lottie 是一種動畫解決方案,簡單說它們就像是動畫 GIF 和 SVG 的混合體。網路上可以找到許多免費資源非常方便使用。

Lottie 是由 Airbnb 開發的開源的解決方案。基本上設計師使用 After Effect 製作動畫,然後通過 Bodymovin 匯出 Lottie JSON 檔案,然後開發者可以在應用中使用相關函式庫並載入執行匯出的檔案。目前支援 Android,iOS,React Native,Windows,Web 等平台。而 LottieFiles 則是一個與 Lottie 相關的平台,提供許多免費和付費 Lottie 動畫。

在 Flutter 中使用 Lottie 套件也是非常簡單。只要加入一行程式就可以呈現動畫:

Lottie.asset('assets/LottieLogo.json')

只要把想要的 Lottie 檔案加入 assets 目錄,然後就可以使用了。

Slivers

slivers 是一種特殊的組件,其中會包含一些底層較為複雜的滾動控制邏輯,主要專門處理滾動內容而設計的。初學 slivers 可能會遭遇挫折,因為和我們之前學習的組件思維有點不同。我們之前在使用 ListView 的時候已經看過 slivers 的實際效果了。

一般來說,但我們使用組件的時候通常大小會是固定的,或者由佈局的父元素限制範圍決定。但是當我們加入滾動效果的時候,情況就不同了,滾動表示內容實際上是超出螢幕或範圍的,也就是我們取消了組件大小的限制。

這時候,一個組件放在可垂直捲動的區域內,那麼它就會變成無限高,Flutter 在處理這種不確定大小的組件時就會遇到問題。

因此如果你要使用 slivers 你需要先認真研究理解,如果你不了解它們的工作原理,可能會反覆遇到錯誤,無法正確解決問題。

列表和導航

為了學習關於 slivers ,我們將從一個範例開始。假設我們希望構建一個列表顯示資訊,然後下方有一個下一頁的按鈕。這裡有兩個需求:

  • 如果列表內容沒有填滿螢幕,那麼下一頁按鈕須在螢幕底部
  • 如果列表內容超出螢幕,那麼列表可以捲動,且在捲到底部的時候可以看到下一頁按鈕

這個情境超級常見,但如果沒有 slivers 會非常難實作。讓我們先從範例開始:

Scaffold(
	body: CustomScrollView(
  	slivers: [
      SliverFixedExtentList(
      	itemExtent: 100.0,
        delegate: SliverChildBuilderDelegate(
        	(BuildContext context, int index) {
            return Container(
            	color: index.isEven ? Colors.red : Colors.black,
            );
          },
          childCount: 3,
        ),
      ),
      SliverFillRemaining(
      	child: Column(
        	mainAxisAlignment: MainAxisAlignment.end,
          children: [
            TextButton(
            	child: Text("下一頁"),
              onPressed: () {}
            ),
          ],
        ),
      )
    ]
  ),
)

在這個範例,我們建立了 Scaffold 裡面包含 CustomScrollView。這個組件引領我們進入 slivers 的世界,目的是讓其包含的東西可以捲動。

顯然,當內容比較多無法用單一螢幕呈現的時候,可捲動是非常重要的功能。CustomScrollViewslivers 參數接受一個陣列,這裡我們使用了兩個 Sliver 相關的組件 SliverFixedExtendListSliverFillRemaining 其中前者類似 ListView 後者則是填充底部剩餘空間。

SliverFillRemaining 通常是最後一個 slivers 組件,它可以填充可捲動區域剩下的空間。

SliverFixedExtendList 的部分稍微比較難理解,但基本上就是通過類似組件 builder 並回傳 SliverChildBuilderDelegate。這個 delegate 委派通過 childCount 設定子元素的數量。接著我們使用 index 來設定不同顏色的 Container

最後,回到 SliverFillRemaining 填充的剩餘空間,我們放入一個 Column 和按鈕。

如此一來,在內容沒那麼多的時候,按鈕如我們的需求會出現在底部。當內容變多超出螢幕範圍的時候,按鈕則被推出螢幕,實際上在可捲動區域的底部。

SliverAppBar

另一個 slivers 常使用的功能就是 SliverAppBar 。類似 ScaffoldAppBar ,但它的高可以根據內容偏移而變化,並且可以根據使用者的操作顯示或隱藏。

SliverAppBar 只是另一個 Sliver 組件,因此也可以放在 CustomScrollView ,例如下面範例:

CustomScrollView(
	slivers: [
    SliverAppBar(
    	expandedHeight: 100,0,
      floating: true,
      title: Text("頁面"),
      flexibleSpace: FlexibleSpaceBar(
      	title: Text("標題")
      ),
      actions: [
        IconButton(
        	icon: Icon(Icons.delete),
          onPressed: () {}
        ),
      ],
    )
  ]
)

這個範例,SliverAppBar 的高會展開到 100px,並且和 AppBar 一樣有標題和動作按鈕,還有 flexibleSpace,這個區域在使用者捲動的時候會縮小。此外,設定了 floating 參數,表示當使用者向下捲動時會隱藏,向上捲動時會在重新出現。

除了上述的用法,還有其他控制 SliverAppBar 在捲動時對應的行為。下面是 3 個主要的參數:

  • floating 控制向上捲動時,是否立即顯示
  • pinned 控制向下捲動時,保持顯示。伸縮區域仍保持展開或收縮。
  • snap 是否快速彈入的效果

光使用文字可能很難具體說明這些效果。可以參考官方說明提供的影片可快速理解組件的效果。


上一篇
Day 14 樣式與佈局
下一篇
Day 16 路由與 Navigator 1.0 vs 2.0
系列文
Flutter 開發實戰 - 30 天逃離新手村38
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言