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

1. 簡介

logo_components_color_2x_web_96dp.png

開發人員可透過 Material 元件 (MDC) 實作 Material Design。MDC 由 Google 的工程師和 UX 設計師團隊建立,提供數十種美觀實用的 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. 新增頂端應用程式列

現在點選「繼續」按鈕,即可看到顯示「完成!」的主畫面。太好了!但現在使用者無法採取任何動作,也無法瞭解自己在應用程式中的位置。為此,我們需要加入導覽功能。

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

應用程式列,標題為「Shrine」

應用程式列,標題為「Shrine」

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

新增開頭的 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

應用程式列,標題為 Shrine,並含有漢堡選單圖示

應用程式列,標題為 Shrine,並含有漢堡選單圖示

選單圖示 (又稱「漢堡」) 會顯示在您預期的位置。

您也可以在標題的尾端新增按鈕。在 Flutter 中,這些項目稱為「動作」。

新增動作

還可容納兩個 IconButtons。

在標題後方將這些項目新增至 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

應用程式列,標題為「Shrine」,並有漢堡選單圖示,以及後方搜尋和自訂圖示

應用程式列,標題為「Shrine」,並有漢堡選單圖示,以及後方搜尋和自訂圖示

現在應用程式有開頭按鈕、標題,以及右側的兩個動作。應用程式列也會使用細微的陰影顯示高度,表示應用程式列位於與內容不同的圖層。

5. 在格線中新增卡片

現在應用程式已具備一些結構,接下來請將內容放入資訊卡,以便整理。

新增 GridView

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

將 Scaffold 主體中的 Center 替換為 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'),
            ],
          ),
        ),
      ],
    ),
  )
],

這段程式碼會新增 Column 小工具,用於垂直排列子項小工具。

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

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

邊框間距會將文字稍微往內移。

這兩個 Text 小工具會垂直堆疊,中間有 8 點的空白空間 (SizedBox)。我們再建立一個 Column,將這些項目放在 Padding 內。

儲存專案。

Android

iOS

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

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

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

資訊卡通常會與其他資訊卡一起顯示在集合中。我們將這些圖片以格狀集合的形式呈現。

6. 製作廣告資訊卡集錦

如果畫面上有多張資訊卡,系統會將這些資訊卡分組為一或多個集合。集合中的資訊卡共平面,也就是說,資訊卡彼此之間共用相同的靜止高度 (除非資訊卡遭到選取或拖曳,但我們不會在這裡執行這項操作)。

將卡片複製到集合

目前我們的 Card 是在 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 元件

非常同意 同意 沒意見 不同意 非常不同意