1. 程式人生 > >[譯] MDC-102 Flutter:Material 結構和佈局(Flutter)

[譯] MDC-102 Flutter:Material 結構和佈局(Flutter)

MDC-102 Flutter:Material 結構和佈局(Flutter)

1. 介紹

Material Components(MDC)幫助開發者實現 Material Design。MDC 由谷歌團隊的工程師和 UX 設計師創造,為 Android、iOS、Web 和 Flutter 提供很多美觀實用的 UI 元件。

material.io/develop

在教程 MDC-101 中,你使用了兩個 Material 元件:文字框和墨水波紋效果的按鈕來構建一個登陸頁面。現在讓我們通過新增導航、結構和資料來拓展應用。

你將要構建

在本教程中,你將為 Shrine —— 一個銷售服裝和家居用品的電子商務應用程式構建一個主頁面。它將含有:

  • 一個位於頂部的應用欄
  • 一個由產品填充的網格列表

這是四篇教程裡的第二篇,它將引導你為 Shrine 的產品構建應用程式。我們建議你按照教程的順序一步一步地編寫你的程式碼。

相關的教程可以在以下位置找到:

到 MDC-104 的最後,你將會構建一個像這樣的應用:

將要用到的 MDC 元件

  • 頂部應用欄(Top app bar)
  • 網格(Grid)
  • 卡片(Card)

本教程中,你將使用 MDC-Flutter 提供的預設元件。你將會在 MDC-103: Material Design Theming 的顏色、形狀、高度和型別中學習如何定製它們。

你將需要

  • Flutter SDK
  • 安裝好 Flutter 外掛的 Android Studio,或者你喜歡的程式碼編輯器
  • 示例程式碼

要在 iOS 上構建和執行 Flutter 應用程式,你需要滿足以下要求:

  • 執行 macOS 的計算機
  • Xcode 9 或更新版本
  • iOS 模擬器,或者 iOS 物理裝置

要在 Android 上構建和執行 Flutter 應用程式,你需要滿足以下要求:

  • 執行 macOS、Windows 或 Linux 的計算機
  • Android Studio
  • Android 模擬器(隨 Android Studio 一起提供)或 Android 物理裝置

2. 安裝 Flutter 環境

前提條件

要開始使用 Flutter 開發移動應用程式,你需要:

  • Flutter SDK
  • 裝有 Flutter 外掛的 IntelliJ IDE,或者你喜歡的程式碼編輯器

Flutter 的 IDE 工具適用於 Android StudioIntelliJ IDEA Community(免費)和 IntelliJ IDEA Ultimate

要在 iOS 上構建和執行 Flutter 應用程式,你需要滿足以下要求:

  • 執行 macOS 的計算機
  • Xcode 9 或更新版本
  • iOS 模擬器,或者 iOS 物理裝置

要在 Android 上構建和執行 Flutter 應用程式,你需要滿足以下要求:

  • 執行 macOS、Windows 或者 Linux 的計算機
  • Android Studio
  • Android 模擬器(隨 Android Studio 一起提供)或 Android 物理裝置

獲取詳細的 Flutter 安裝資訊

重要提示:如果連線到計算機的 Android 手機上出現“允許 USB 除錯”對話方塊,請啟用始終允許從此計算機選項,然後單擊確定

在繼續本教程之前,請確保你的 SDK 處於正確的狀態。如果之前安裝過 Flutter,則使用 flutter upgrade 來確保 SDK 處於最新版本。

flutter upgrade
複製程式碼

執行 flutter upgrade 將自動執行 flutter doctor。如果這是首次安裝 Flutter 且不需升級,那麼請手動執行 flutter doctor。檢視顯示的所有檢查標記;這將會下載你需要的任何缺少的 SDK 檔案,並確保你的計算機配置無誤以進行 Flutter 的開發。

flutter doctor
複製程式碼

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
git checkout 102-starter_and_101-complete
複製程式碼

更多幫助:從 GitHub 上克隆儲存庫

正確的分支

教程 MDC-101 到 104 連續構建。所以當你完成 102 的程式碼後,它將變成 103 教程的初始程式碼!程式碼被分成不同的分支,你可以使用以下命令將它們全部列出:

git branch --list

要檢視完整程式碼,請切換到 103-starter_and_102-complete 分支。

建立你的專案

以下步驟預設你使用的是 Android Studio (IntelliJ)。

建立專案

  1. 在終端中,導航到 material-components-flutter-codelabs

  2. 執行 flutter create mdc_100_series

開啟專案

  1. 開啟 Android Studio。

  2. 如果你看到歡迎頁面,單擊 開啟已有的 Android Studio 專案

  1. 導航到 material-components-flutter-codelabs/mdc_100_series 目錄並單擊開啟,這將開啟此專案。

在構建專案一次之前,你可以忽略在分析中見到的任何錯誤。

  1. 在左側的專案面板中,刪除測試檔案 ../test/widget_test.dart

  1. 如果出現提示,安裝所有平臺和外掛更新或 FlutterRunConfigurationType,然後重新啟動 Android Studio。

提示:確保你已安裝 Flutter 和 Dart 外掛

執行初始程式

以下步驟預設你在 Android 模擬器或裝置上進行測試。你也可以在 iOS 模擬器或裝置上進行,只要你安裝了 Xcode。

  1. 選擇裝置或模擬器

如果 Android 模擬器尚未執行,請選擇 Tools -> Android -> AVD Manager建立您裝置並啟動模擬器。如果 AVD 已存在,你可以直接在 IntelliJ 的裝置選擇器中啟動模擬器,如下一步所示。

(對於 iOS 模擬器,如果它尚未執行,通過選擇 Flutter Device Selection -> Open iOS Simulator 來在你的開發裝置上啟動它。)

  1. 啟動 Flutter 應用:
  • 在你的編輯器視窗頂部尋找 Flutter Device Selection 下拉選單,然後選擇裝置(例如,iPhone SE / Android SDK built for )。
  • 點選執行圖示(
    )。

如果你無法成功執行此應用程式,停下來解決你的開發環境問題。嘗試導航到 material-components-flutter-codelabs;如果你在終端中下載 .zip 檔案,導航到 material-components-flutter-codelabs-... 然後執行 flutter create mdc_100_series

成功!Shrine 的初始登陸程式碼應該在你的模擬器中運行了。你可以看到 Shrine 的 logo 和它下面的名稱 "Shrine"。

現在登入頁面看起來不錯,讓我們用一些產品來填充應用。

4. 新增頂部應用欄

當登陸頁面消失時主頁面將出現並顯示“你做到了!”。這很棒!但是我們的使用者不知道能做什麼操作,也不知道現在位於應用何處,為了解決這個問題,是時候新增導航了。

導航 是指允許使用者在應用中移動的元件、互動、視覺提示和資訊結構。它使得內容和功能更加註目,任務也因此易於完成。

在 Material 指南中瞭解更多有關導航的資訊。

Material Design 提供確保高度可用性的導航模式,其中最注目的元件就是頂部應用欄。

你可以將頂部應用欄當作 iOS 中的“導航欄”,或者簡單看成一個 “App Bar” 或 “Header”。

要提供導航並讓使用者快速訪問其他操作,讓我們新增一個頂部應用欄。

新增應用欄部件

home.dart 中,將應用欄新增到 Scaffold 中:

return Scaffold(
  // TODO: 新增應用欄(102)
  appBar: AppBar(
    // TODO: 新增按鈕和標題(102)
  ),
複製程式碼

AppBar 新增到 Scaffold 的 appBar: 欄位位置,為了我們完美的佈局,讓應用欄保持在頁面的頂部或底部。

Scaffold 在中是一個重要的部件。它為像抽屜、snack bar 和 bottom sheet 等各種常見 Material 元件提供方便的 API。它甚至可以幫助佈置一個 Floating Action Button。

Flutter 文件中瞭解更多有關 Scaffold 的資訊。

儲存專案,當 Shrine 應用更新後,單擊 Next 來檢視主螢幕。

應用欄看起來不錯,但它還需要一個標題。

如果應用沒有更新,再次單擊 “Play” 按鈕,或者點選 “Play” 後的 “Stop”。

新增文字部件

home.dart 中,給應用欄新增一個標題:

// TODO: 新增應用欄(102)  
  appBar: AppBar(
    // TODO: 新增按鈕和標題(102)

    title: Text('SHRINE'),
        // TODO:新增後續按鈕(102)
複製程式碼

儲存專案。

到目前為止,你應該已經注意到我們所說的“平臺差異”了。Material 明白 Android、iOS、Web 各平臺都有差異。使用者對他們有不同的期望。舉例來說,在 iOS 裡標題幾乎總是居中的,這是 UIKit 提供的預設配置。在 Android 上標題是左對齊的。所以如果你使用的是 Android 模擬器或裝置,那麼標題應該位於左側,對於 iOS 模擬器和裝置而言,它應該是居中的。

瞭解更多資訊,請查參閱有關跨平臺適配的 Material 文章

許多應用欄在標題旁邊都設有按鈕,讓我們在應用中新增一個選單圖示。

新增位於首部的圖示按鈕

還是在 home.dart 中,在 AppBar 的 leading 欄位設定一個圖示按鈕:(放在 title: 欄位前,按照部件從首到尾的順序):

return Scaffold(
  appBar: AppBar(
    // TODO: 新增按鈕和標題(102)
    leading: IconButton(
      icon: Icon(
        Icons.menu,
        semanticLabel: 'menu',
      ),
      onPressed: () {
        print('Menu button');
      },
    ),
複製程式碼

儲存專案。

選單圖示(也被稱作“漢堡包”)會在你期望的位置顯示出來。

IconButton 類是在你的應用裡引入 Material 圖示的快捷方式。它有一個 Icon 部件。 Flutter 在 Icons 類裡有整套的圖示。它會根據字串常量的對映自動匯入圖示。

Flutter 文件中瞭解更多有關 Icons 類的資訊。有關 Icon 部件的資訊請閱讀這個 Flutter 文件

你也可以在標題尾部新增按鈕。在 Flutter 中,它們被稱為 "action"。

Leading(首部)trailing(尾部) 是表達方向的術語,指的是與語言無關的文字行的開頭和結尾。當使用一個像英語這樣的 LTR(左到右)語言時, leading 意味著 左側trailing 代表著 右側。在像阿拉伯語這樣的 RTL(右到左)語言時, leading 意味著 右側trailing 代表著 左側

瞭解 UI 映象的更多資訊,請參閱 雙向性 Material Design 準則。

新增 action

還有兩個 IconButton 的空間。

在 AppBar 例項中的標題後面新增它們:

// TODO: 新增尾部按鈕(102)
actions: <Widget>[
  IconButton(
    icon: Icon(
      Icons.search,
      semanticLabel: 'search',
    ),
    onPressed: () {
      print('Search button');
    },
  ),
  IconButton(
    icon: Icon(
      Icons.tune,
      semanticLabel: 'filter',
    ),
    onPressed: () {
      print('Filter button');
    },
  ),
],
複製程式碼

儲存你的專案。你的主螢幕看起來應該像這樣:

現在這個應用在左側有一個按鈕、一個標題,右側還有兩個 action。應用欄還利用陰影顯示高度,表示它與內容處於不同的層級。

在 Icon 類中,SemanticLabel 欄位是在 Flutter 中新增輔助功能資訊的常用方法。這很像 Android 的 Content Label 或 iOS 的 UIAccessibility accessibilityLabel。你會在很多類中見到它。

這個欄位的資訊很好地向使用螢幕閱讀器的人說明了該按鈕的作用。

對於沒有 semanticLabel: 欄位的部件,你可以將其包裝在 Semantics 部件中,在其 Flutter 文件中瞭解更多有關的資訊。

5. 在網格中新增卡片

現在我們的應用像點樣子了,讓我們接著放置一些卡片來組織內容。

卡片 是顯示單體內容和動作的獨立的元素。它們是一種可以靈活地呈現近似內容集合的方式。

在 Material 指南有關卡片的文章中瞭解更多資訊。

要了解卡片部件,請參閱在 Flutter 中構建佈局

新增網格檢視

讓我們從應用欄底部新增一個卡片開始。單一的 卡片 部件不足以讓我們將它放到我們想要的位置,所以我們需要將它封裝在一個 網格檢視 中。

用 GridView 替換 Scaffold 中 body 欄位的 Center:

// TODO: 新增網格檢視(102)
body: GridView.count(
  crossAxisCount: 2,
  padding: EdgeInsets.all(16.0),
  childAspectRatio: 8.0 / 9.0,
  // TODO: 構建一組卡片(102)
  children: <Widget>[Card()],
),
複製程式碼

讓我們分析這段程式碼。網格檢視呼叫 count() 建構函式,因要新增的專案數是可數的而不是無限的。但它需要更多資訊來定義其佈局。

crossAxisCount: 指定橫向顯示數目,我們設定成 2 行。

Flutter 中的 Cross axis(橫軸) 表示非滾動軸。可滾動的方向稱為 主軸。所以如果你的應用像網格檢視預設的那樣垂直滾動,那麼橫軸就是水平方向。

詳情請參閱構建佈局

padding: 欄位為網格檢視的 4 條邊設定填充。當然你現在看不到首尾的填充,因為網格檢視內還沒有其他子項。

childAspectRatio: 欄位依據寬高比確定其大小。

預設地,網格檢視中的專案尺寸相同。

將這些加在一起,網格檢視按照如下方式計算每個子項的寬度:([整個網格寬度] - [左填充] - [右填充]) / 列數。在這裡就是:([整個網格寬度] - 16 - 16) / 2

高度是根據寬度計算得來的,通過應用寬高比:([整個網格寬度] - 16 - 16) / 2 * 9 / 8。我們翻轉了 8 和 9,因為我們是用寬度來計算高度。

我們已經有了一個空的卡片了,讓我們新增一些子部件到卡片中。

佈局內容

卡片內應該包含一張圖片、一個標題和一個次級文字。

更新網格檢視的子項:

// TODO: 構建一組卡片(102)
children: <Widget>[
  Card(
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        AspectRatio(
          aspectRatio: 18.0 / 11.0,
          child: Image.asset('assets/diamond.png'),
        ),
        Padding(
          padding: EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              Text('Title'),
              SizedBox(height: 8.0),
              Text('Secondary Text'),
            ],
          ),
        ),
      ],
    ),
  )
],
複製程式碼

這段程式碼添加了一個列部件,用來垂直地佈局子部件。

crossAxisAlignment: 欄位指定 CrossAxisAlignment.start 屬性,這意味著“文字與前沿對齊”。

AspectRatio 部件決定影象的形狀,無論提供的是何種影象。

Padding 使得文字與邊框保持一定距離。

兩個 Text 部件垂直堆疊,在其間保持 8 個單位的間隔(SizedBox)。我們使用另一個 Column 來把它們放到 Padding 中。

儲存你的專案:

在這個預覽裡,你可以看到卡片從邊緣置入,並帶有圓角和陰影(這代表著卡片的高度)。整個形狀在 Material 中被稱為 “container(容器)”。(不要與名為 Container 的實際部件類混淆。)

除了容器以外,在 Material 中卡片內所有的元素實際上都是可選的。你可以新增標題文字、縮圖、頭像或者小標題文字、分隔符甚至是按鈕和圖示。

瞭解更多訊息,請參閱 Material 指南上有關卡片的文章。

卡片經常以集合的形式和其他卡片一起出現,讓我們在網格檢視中給它們佈局。

6. 生成卡片集合

每當螢幕上出現多張卡片時,它們就會組成一個或多個集合。集合中的卡片是共面的,這意味著卡片共享相同的靜止高度。(除了卡片被拾起或拖動,但在這裡我們不會這麼做。)

將卡片新增到集合

現在我們的卡片是網格檢視內的 children: 欄位子項。這有一大段難以閱讀的巢狀程式碼。讓我們將它提取到一個函式中來生成任意數量的空卡片,然後返回給我們。

// TODO: 生成卡片集合(102)
List<Card> _buildGridCards(int count) {
  List<Card> cards = List.generate(
    count,
    (int index) => Card(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          AspectRatio(
            aspectRatio: 18.0 / 11.0,
            child: Image.asset('assets/diamond.png'),
          ),
          Padding(
            padding: EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                Text('Title'),
                SizedBox(height: 8.0),
                Text('Secondary Text'),
              ],
            ),
          ),
        ],
      ),
    ),
  );

  return cards;
}
複製程式碼

將生成的卡片分配給網格檢視的 children 欄位。記得用新程式碼替換網格檢視中的所有內容。

// TODO: 新增網格檢視(102)
body: GridView.count(
  crossAxisCount: 2,
  padding: EdgeInsets.all(16.0),
  childAspectRatio: 8.0 / 9.0,
  children: _buildGridCards(10) // 替換所有內容
),
複製程式碼

儲存你的專案:

卡片已經在這了,但它們什麼都沒有顯示。現在是時候新增一些產品資料了。

###新增產品資料

這個應用中的產品有著影象、名稱和價格。讓我們把這些新增到已有的卡片部件中。

然後,在 home.dart 中,匯入資料模型需要的新包和檔案:

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

import 'model/products_repository.dart';
import 'model/product.dart';
複製程式碼

最後,更改 _buildGridCards() 來獲取產品資訊,並將資料應用到卡片中:

// TODO: 生成卡片集合(102)

// 替換整個方法
List<Card> _buildGridCards(BuildContext context) {
  List<Product> products = ProductsRepository.loadProducts(Category.all);

  if (products == null || 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(
      // TODO: 調整卡片高度(103)
      child: Column(
        // TODO: 卡片的內容設定居中(103)
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          AspectRatio(
            aspectRatio: 18 / 11,
            child: Image.asset(
              product.assetName,
              package: product.assetPackage,
             // TODO: 調整盒子尺寸(102)
            ),
          ),
          Expanded(
            child: Padding(
              padding: EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),
              child: Column(
               // TODO: 標籤底部對齊並居中(103)
               crossAxisAlignment: CrossAxisAlignment.start,
                // TODO: 更改最內部的列(103)
                children: <Widget>[
                 // TODO: 處理溢位的標籤(103)
                 Text(
                    product.name,
                    style: theme.textTheme.title,
                    maxLines: 1,
                  ),
                  SizedBox(height: 8.0),
                  Text(
                    formatter.format(product.price),
                    style: theme.textTheme.body2,
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }).toList();
}
複製程式碼

注意:應用現在無法編譯和執行,我們還需要進行修改。

要設定文字的樣式,我們使用當前 BuildContext 中的 ThemeData

瞭解有關文字樣式的更多資訊,請參閱 Material 指南中的排版一文。瞭解有關主題的更多資訊,請參考教程下一章 MDC-103: Material Design Theming 的顏色、形狀、高度和型別

在嘗試編譯之前,將 BuildContext 傳入 build() 方法中的 _buildGridCards()

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

你可能注意到了我們沒有在卡片間新增任何垂直的間隔,這是因為在其頂部與底部預設有 4 個單位的填充。

儲存你的專案:

產品的資料顯示出來了,但是影象四周有額外的空間。影象預設依據 .scaleDownBoxFit 繪製(在這個情況下)。讓我們將其更改為 .fitWidth 來讓它們放大一點,刪除多餘的空間。

修改影象的 fit: 欄位:

  // TODO: 調整盒子尺寸(102)
  fit: BoxFit.fitWidth,
複製程式碼

現在我們的產品完美的展現在應用中了!

7. 總結

我們的應用已經有了基本的流程,將使用者從登陸螢幕帶到可以檢視產品的主螢幕。通過幾行程式碼,我們添加了一個頂部應用欄(帶有標題和三個按鈕)以及卡片(用於顯示我們應用的內容)。我們的主螢幕簡潔實用,具有基本的結構和可操作的內容。

完成的 MDC-102 應用可以在 103-starter_and_102-complete 分支中找到。

你可以用此分支下的應用來對照驗證你的版本。

下一步

通過頂部應用欄、卡片、文字框和按鈕,我們已經使用了 MDC-Flutter 庫中的四個核心元件!你可以訪問 Flutter 部件目錄來探索更多元件。

雖然它完全正常執行,我們的應用尚未表達任何特殊的品牌特點。在 MDC-103: Material Design Theming 的顏色、形狀、高度和型別中,我們將定製這些元件的樣式,來詮釋一個充滿活力的、現代的品牌。

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄