MDC-102 Flutter:Material 結構和版面配置

1. 簡介

logo_components_color_2x_web_96dp.png

Material 元件 (MDC) 可協助開發人員實作 Material Design。MDC 是由 Google 工程師和使用者體驗設計師團隊打造,提供數十種精美且實用的 UI 元件,適用於 Android、iOS、網頁和 Flutter.material.io/develop

MDC-101 程式碼研究室中,您使用了兩個 Material 元件來建立登入頁面:文字欄位和帶有墨水波紋的按鈕。接下來,我們將在這個基礎上加入導覽、結構和資料。

建構項目

在本程式碼研究室中,您會為名為 Shrine 的電子商務應用程式建構主畫面,這是一款用於販售服飾和居家用品的電子商務應用程式。其中包含:

  • 頂端應用程式列
  • 完整的產品格狀清單

Android

iOS

電子商務應用程式,顯示頂端應用程式列,以及各式各樣的產品格狀檢視畫面

電子商務應用程式,頂端應用程式列和產品格線

本程式碼研究室中的 Material Flutter 元件和子系統

  • 頂端應用程式列
  • 格線
  • 資訊卡

針對 Flutter 開發經驗,您會給予什麼評價?

初級 中級 適合

2. 設定 Flutter 開發環境

您需要兩個軟體才能完成這個實驗室活動,分別是 Flutter SDK編輯器

您可以使用下列任一裝置執行程式碼研究室:

  • 連接至電腦的實體 AndroidiOS 裝置,並設為開發人員模式。
  • iOS 模擬器 (需要安裝 Xcode 工具)。
  • Android Emulator (需要在 Android Studio 中設定)。
  • 瀏覽器 (偵錯時必須使用 Chrome)。
  • 下載 WindowsLinuxmacOS 桌面應用程式。您必須在預計部署的平台上進行開發。因此,如果您想要開發 Windows 電腦版應用程式,就必須在 Windows 上進行開發,以便存取適當的建構鏈結。如要進一步瞭解作業系統的特定需求,請參閱 docs.flutter.dev/desktop

3. 下載程式碼研究室的範例應用程式

您使用的是 MDC-101 版本嗎?

如果您已完成 MDC-101 課程,您的程式碼應已準備好用於本程式碼研究室。跳至步驟:新增頂端應用程式列

從頭開始嗎?

下載入門程式碼研究室應用程式

範例應用程式位於 material-components-flutter-codelabs-102-starter_and_101-complete/mdc_100_series 目錄中。

...或是從 GitHub 複製檔案

如要從 GitHub 複製本程式碼研究室,請執行下列指令:

git clone https://github.com/material-components/material-components-flutter-codelabs.git
cd material-components-flutter-codelabs/mdc_100_series
git checkout 102-starter_and_101-complete

開啟專案並執行應用程式

  1. 在您選擇的編輯器中開啟專案。
  2. 按照所選編輯器的「開始使用:測試」部分,按照「執行應用程式」的說明操作。

大功告成!您應該會在裝置上的 MDC-101 程式碼研究室中看到 Shrine 登入頁面。

Android

iOS

登入頁面,包含使用者名稱和密碼欄位、取消和下一步按鈕

登入頁面,包含使用者名稱和密碼欄位、取消和下一步按鈕

登入畫面看起來不錯,現在讓我們在應用程式中填入一些產品。

4. 新增頂端應用程式列

目前,如果您按一下「Next」按鈕,就會看到主畫面顯示「You did it!」(您成功了!)。太好了!但使用者現在不需要採取任何行動,或全盤知道自己在應用程式中的哪個位置。為了提供協助,請開始新增導覽功能。

Material Design 提供的瀏覽模式可確保高度可用性。最常見的元件之一就是頂端應用程式列。

如要提供導覽選項,並讓使用者快速存取其他動作,請加入頂端應用程式列。

新增 AppBar 小工具

home.dart 中,將 AppBar 新增至 Scaffold,並移除醒目顯示的 const

return const Scaffold(
  // TODO: Add app bar (102)
  appBar: AppBar(
    // TODO: Add buttons and title (102)
  ),

AppBar 新增至 Scaffold 的 appBar: 欄位,即可免費獲得完美的版面配置,讓 AppBar 位於頁面頂端,而內文位於下方。

新增文字小工具

home.dart 中,為 AppBar 新增標題:

// TODO: Add app bar (102)
  appBar: AppBar(
    // TODO: Add buttons and title (102)
    title: const Text('SHRINE'),
    // TODO: Add trailing buttons (102)

儲存專案。

Android

iOS

標題為「神社」的應用程式列

標題為「神社」的應用程式列

許多應用程式列的標題旁邊都有按鈕。我們要在應用程式中新增選單圖示。

新增前置 IconButton

home.dart 中,為 AppBar 的 leading: 欄位設定 IconButton。(請將其放在 title: 欄位前,模擬從前到後的順序):

    // TODO: Add buttons and title (102)
    leading: IconButton(
      icon: const Icon(
        Icons.menu,
        semanticLabel: 'menu',
      ),
      onPressed: () {
        print('Menu button');
      },
    ),

儲存專案。

Android

iOS

應用程式列,標題為「神社」的標題,「漢堡選單」圖示

應用程式列,標題為「神社」的標題,「漢堡選單」圖示

選單圖示 (又稱為「漢堡」) 會顯示在所需位置。

您也可以在標題後方加入按鈕。在 Flutter 中,這些稱為「動作」。

新增動作

剩下 2 個 IconButton。

將這些元素加入 AppBar 例項的標題後方:

// TODO: Add trailing buttons (102)
actions: <Widget>[
  IconButton(
    icon: const Icon(
      Icons.search,
      semanticLabel: 'search',
    ),
    onPressed: () {
      print('Search button');
    },
  ),
  IconButton(
    icon: const Icon(
      Icons.tune,
      semanticLabel: 'filter',
    ),
    onPressed: () {
      print('Filter button');
    },
  ),
],

儲存專案。主畫面應如下所示:

Android

iOS

應用程式列顯示「神社」的標題和三橫線選單圖示,以及結尾的搜尋與自訂圖示

應用程式列顯示「神社」的標題和三橫線選單圖示,以及結尾的搜尋與自訂圖示

現在應用程式右側有一個前置按鈕、標題,以及兩個動作。應用程式列也可以使用細微的陰影顯示「高度」,指出所在位置與內容位於不同層。

5. 在格狀中新增資訊卡

應用程式有了一些結構後,我們現在要將內容放入資訊卡來進行整理。

新增 GridView

讓我們先在頂端應用程式列下方新增一張資訊卡。單獨的 Card 小工具沒有足夠的資訊,無法在我們可見的位置進行版面配置,因此我們會將其封裝在 GridView 小工具中。

將 Scaffold 內文中的中心替換為 GridView:

// TODO: Add a grid view (102)
body: GridView.count(
  crossAxisCount: 2,
  padding: const EdgeInsets.all(16.0),
  childAspectRatio: 8.0 / 9.0,
  // TODO: Build a grid of cards (102)
  children: <Widget>[Card()],
),

讓我們來解壓縮這段程式碼。GridView 會叫用 count() 建構函式,因為其顯示的項目數量可以計數,而非無限量。但需要更多資訊才能定義其版面配置。

crossAxisCount: 會指定其中的項目數量。您想要 2 欄。

padding: 欄位會在 GridView 的全部 4 邊提供空間。當然,您看不到結尾或底部的邊框間距,因為它們旁邊還沒有 GridView 子項。

childAspectRatio: 欄位會根據顯示比例 (寬度大於高度) 識別項目大小。

根據預設,GridView 會讓所有大小相同的圖塊。

我們有張卡片,但沒有任何內容。讓我們在資訊卡中新增子項小工具。

版面配置內容

資訊卡應包含圖片、標題和次要文字的區域。

更新 GridView 的子項:

// TODO: Build a grid of cards (102)
children: <Widget>[
  Card(
    clipBehavior: Clip.antiAlias,
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        AspectRatio(
          aspectRatio: 18.0 / 11.0,
          child: Image.asset('assets/diamond.png'),
        ),
        Padding(
          padding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              Text('Title'),
              const SizedBox(height: 8.0),
              Text('Secondary Text'),
            ],
          ),
        ),
      ],
    ),
  )
],

此程式碼會新增「欄」小工具,用來垂直排列子項小工具。

crossAxisAlignment: field 指定 CrossAxisAlignment.start,表示「將文字對齊前緣」。

無論您提供何種圖片,AspectRatio 小工具都會決定圖片的形狀。

「邊框間距」能從側邊產生文字。

這兩個「Text」小工具彼此垂直堆疊,彼此之間留有 8 個空白處 (SizedBox)。我們建立另一個 Column,用來存放邊框間距。

儲存專案。

Android

iOS

含有圖片、標題和次要文字的單一項目

含有圖片、標題和次要文字的單一項目

在這個預覽畫面中,您可以看到資訊卡從邊緣插入到邊緣,並有圓角和陰影 (代表卡片高度)。整個形狀在 Material 中稱為「容器」。(請勿與名為 Container 的實際小工具類別混淆)。

資訊卡通常會與其他資訊卡一同顯示在集合中。讓我們將這些圖片以集合形式排列在格狀檢視畫面中。

6. 建立資訊卡集

當畫面顯示多張資訊卡時,系統會將這些資訊卡歸入一或多個集合。集合中的資訊卡是共平面,也就是說,這些資訊卡會共用相同的靜止高度 (除非資訊卡被提起或拖曳,但我們不會在這裡這樣做)。

將資訊卡乘以集合

目前資訊卡是透過 GridView 的 children: 欄位內嵌建構。這會產生大量難以閱讀的巢狀程式碼。讓我們將其擷取至函式,以便產生任意數量的空白資訊卡,並傳回資訊卡清單。

build() 函式上方建立新的私人函式 (請注意,以底線開頭的函式為不公開 API):

// TODO: Make a collection of cards (102)
List<Card> _buildGridCards(int count) {
  List<Card> cards = List.generate(
    count,
    (int index) {
      return Card(
        clipBehavior: Clip.antiAlias,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            AspectRatio(
              aspectRatio: 18.0 / 11.0,
              child: Image.asset('assets/diamond.png'),
            ),
            Padding(
              padding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: const <Widget>[
                  Text('Title'),
                  SizedBox(height: 8.0),
                  Text('Secondary Text'),
                ],
              ),
            ),
          ],
        ),
      );
    },
  );
  return cards;
}

將產生的資訊卡指派給 GridView 的 children 欄位。請記得用這個新程式碼取代 GridView 中的所有內容

// TODO: Add a grid view (102)
body: GridView.count(
  crossAxisCount: 2,
  padding: const EdgeInsets.all(16.0),
  childAspectRatio: 8.0 / 9.0,
  children: _buildGridCards(10) // Replace
),

儲存專案。

Android

iOS

內含圖片、標題和次要文字的項目格線

內含圖片、標題和次要文字的項目格線

卡片已顯示,但還沒有任何內容。接著,請新增產品資料。

新增產品資料

這個應用程式中有部分產品附有圖片、名稱和價格。讓我們將這項資訊加入目前已在資訊卡中顯示的小工具

然後在 home.dart 中,匯入新的套件和我們為資料模型提供的一些檔案:

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';

import 'model/product.dart';
import 'model/products_repository.dart';

最後,變更 _buildGridCards() 以擷取產品資訊,並在資訊卡中使用該資料:

// TODO: Make a collection of cards (102)

// Replace this entire method
List<Card> _buildGridCards(BuildContext context) {
  List<Product> products = ProductsRepository.loadProducts(Category.all);

  if (products.isEmpty) {
    return const <Card>[];
  }

  final ThemeData theme = Theme.of(context);
  final NumberFormat formatter = NumberFormat.simpleCurrency(
      locale: Localizations.localeOf(context).toString());

  return products.map((product) {
    return Card(
      clipBehavior: Clip.antiAlias,
      // TODO: Adjust card heights (103)
      child: Column(
        // TODO: Center items on the card (103)
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          AspectRatio(
            aspectRatio: 18 / 11,
            child: Image.asset(
              product.assetName,
              package: product.assetPackage,
             // TODO: Adjust the box size (102)
            ),
          ),
          Expanded(
            child: Padding(
              padding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),
              child: Column(
               // TODO: Align labels to the bottom and center (103)
               crossAxisAlignment: CrossAxisAlignment.start,
                // TODO: Change innermost Column (103)
                children: <Widget>[
                 // TODO: Handle overflowing labels (103)
                 Text(
                    product.name,
                    style: theme.textTheme.titleLarge,
                    maxLines: 1,
                  ),
                  const SizedBox(height: 8.0),
                  Text(
                    formatter.format(product.price),
                    style: theme.textTheme.titleSmall,
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }).toList();
}

注意:不會編譯及執行。我們還有一項變更

此外,在嘗試編譯之前,變更 build() 函式,將 BuildContext 傳遞至 _buildGridCards()

// TODO: Add a grid view (102)
body: GridView.count(
  crossAxisCount: 2,
  padding: const EdgeInsets.all(16.0),
  childAspectRatio: 8.0 / 9.0,
  children: _buildGridCards(context) // Changed code
),

熱啟動應用程式。

Android

iOS

包含圖片、產品名稱和價格的項目資訊格線

包含圖片、產品名稱和價格的項目資訊格線

您可能會注意到,我們沒有在資訊卡之間加入任何垂直間距。這是因為這類廣告的頂端和底部設有 4 個邊界。

儲存專案。

產品資料會顯示,但圖片周圍會多出額外的空間。根據預設,圖片會以 .scaleDownBoxFit 繪製。我們將其變更為 .fitWidth,讓圖片稍微放大並移除多餘的空白空間。

fit: 欄位新增至圖片,並將值設為 BoxFit.fitWidth

  // TODO: Adjust the box size (102)
  fit: BoxFit.fitWidth,

Android

iOS

包含裁剪圖片、產品名稱和價格的項目方格

我們的產品現在能完美顯示在應用程式中!

7. 恭喜!

我們的應用程式具備基本流程,能將使用者導向登入畫面的主畫面,供使用者查看產品。在幾行程式碼中,我們加入了頂端應用程式列 (含標題和三個按鈕) 和資訊卡 (用來呈現應用程式的內容)。我們的首頁現在簡單實用,具備基本結構和可執行內容。

後續步驟

透過頂端應用程式列、資訊卡、文字欄位和按鈕,我們現已使用 Material Flutter 程式庫中的四個核心元件!詳情請參閱 Material 元件小工具目錄

雖然應用程式已完全運作,但尚未表達任何特定品牌或觀點。在 MDC-103:Material Design 主題設定搭配顏色、形狀、高度和類型,我們將自訂這些元件的樣式,呈現一種鮮豔的現代品牌。

我可以在合理的時間內,完成本程式碼研究室

非常同意 同意 中立 不同意 非常不同意

我想日後繼續使用 Material Design 元件

非常同意 同意 無意見 不同意 非常不同意