- 原文地址:MDC-102 Flutter: Material Structure and Layout (Flutter)
- 原文作者:codelabs.developers.google.com
- 譯文出自:掘金翻譯計劃
- 本文永久連結:github.com/xitu/gold-m…
- 譯者:DevMcryYu
- 校對者:Rickon
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-101: Material Components(MDC)基礎
- MDC-103: Material Design Theming 的顏色、形狀、高度和型別
- MDC-104: Material Design 高階元件。
到 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 Studio、IntelliJ IDEA Community(免費)和 IntelliJ IDEA Ultimate。
要在 iOS 上構建和執行 Flutter 應用程式,你需要滿足以下要求:
- 執行 macOS 的計算機
- Xcode 9 或更新版本
- iOS 模擬器,或者 iOS 物理裝置
要在 Android 上構建和執行 Flutter 應用程式,你需要滿足以下要求:
- 執行 macOS、Windows 或者 Linux 的計算機
- Android Studio
- Android 模擬器(隨 Android Studio 一起提供)或 Android 物理裝置
重要提示:如果連線到計算機的 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)。
建立專案
-
在終端中,導航到
material-components-flutter-codelabs
-
執行
flutter create mdc_100_series
開啟專案
-
開啟 Android Studio。
-
如果你看到歡迎頁面,單擊 開啟已有的 Android Studio 專案。
- 導航到
material-components-flutter-codelabs/mdc_100_series
目錄並單擊開啟,這將開啟此專案。
在構建專案一次之前,你可以忽略在分析中見到的任何錯誤。
- 在左側的專案皮膚中,刪除測試檔案
../test/widget_test.dart
- 如果出現提示,安裝所有平臺和外掛更新或 FlutterRunConfigurationType,然後重新啟動 Android Studio。
提示:確保你已安裝 Flutter 和 Dart 外掛。
執行初始程式
以下步驟預設你在 Android 模擬器或裝置上進行測試。你也可以在 iOS 模擬器或裝置上進行,只要你安裝了 Xcode。
- 選擇裝置或模擬器
如果 Android 模擬器尚未執行,請選擇 Tools -> Android -> AVD Manager 來建立您裝置並啟動模擬器。如果 AVD 已存在,你可以直接在 IntelliJ 的裝置選擇器中啟動模擬器,如下一步所示。
(對於 iOS 模擬器,如果它尚未執行,通過選擇 Flutter Device Selection -> Open iOS Simulator 來在你的開發裝置上啟動它。)
- 啟動 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 個單位的填充。
儲存你的專案:
產品的資料顯示出來了,但是影像四周有額外的空間。影像預設依據 .scaleDown
的 BoxFit 繪製(在這個情況下)。讓我們將其更改為 .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 連結。
掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。