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

1. 簡介

logo_components_color_2x_web_96dp.png

Material Design 元件 (MDC) 可協助開發人員實作質感設計。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

包含使用者名稱和密碼欄位的登入頁面,以及「Cancel」按鈕和「Next」按鈕

包含使用者名稱和密碼欄位的登入頁面,以及「Cancel」按鈕和「Next」按鈕

現在登入畫面看起來沒問題,讓我們在應用程式中填入部分產品。

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

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

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

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

新增前置圖示

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 Design 中(請不要和實際的小工具類別混淆,稱為 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,讓物件能稍微放大,並移除多餘的空白字元。

在圖片中新增值為 BoxFit.fitWidthfit: 欄位:

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

Android

iOS

格線,顯示裁剪圖片、產品名稱和價格

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

7. 恭喜!

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

後續步驟

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

且在完整運作的情況下,應用程式無法呈現任何特定品牌或觀點。在 MDC-103:Material Design 主題設定搭配顏色、形狀、高度和類型,我們將自訂這些元件的樣式,呈現一種鮮豔的現代品牌。

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

非常同意 同意 普通 不同意 非常不同意

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

非常同意 同意 普通 不同意 非常不同意