構建一個 Flutter 佈局
學習如何在 Flutter 中構建佈局。
本教程解釋瞭如何在 Flutter 中設計和構建佈局。
如果您使用提供的示例程式碼,您可以構建以下應用。
完成的應用。
照片由 Dino Reichmuth 在 Unsplash 上拍攝。文字由 瑞士旅遊局 提供。
為了更好地瞭解佈局機制,請從 Flutter 的佈局方法 開始。
繪製佈局圖
#在本節中,請考慮您的應用使用者希望獲得哪種使用者體驗。
考慮如何定位使用者介面的元件。佈局由這些定位的總結果組成。考慮規劃您的佈局以加快編碼速度。使用視覺提示來了解某個元素在螢幕上的位置非常有幫助。
使用您喜歡的任何方法,例如介面設計工具或鉛筆和紙。在編寫程式碼之前,確定您希望將元素放置在螢幕上的位置。這是程式設計版的諺語:“三思而後行”。
提出這些問題來將佈局分解為基本元素。
- 您能識別出行和列嗎?
- 佈局是否包含網格?
- 是否有重疊元素?
- UI 是否需要選項卡?
- 您需要對齊、填充還是邊框嗎?
識別較大的元素。在本例中,您將影像、標題、按鈕和描述排列成一列。
佈局中的主要元素:影像、行、行和文字塊
繪製每一行。
第 1 行,標題 部分,有三個子項:一列文字、一個星形圖示和一個數字。它的第一個子項,該列,包含兩行文字。該第一列可能需要更多空間。
帶有文字塊和圖示的標題部分
第 2 行,按鈕 部分,有三個子項:每個子項包含一個列,然後該列包含一個圖示和一個文字。
帶有三個標記按鈕的按鈕部分
繪製佈局圖後,請考慮如何對其進行編碼。
您會將所有程式碼都寫在一個類中嗎?或者,您會為佈局的每個部分建立一個類嗎?
為了遵循 Flutter 最佳實踐,為佈局的每個部分建立一個類,或 Widget。當 Flutter 需要重新渲染 UI 的一部分時,它會更新發生更改的最小部分。這就是為什麼 Flutter 將“一切都做成 Widget”。如果 Text Widget 中的文字發生更改,Flutter 只會重新繪製該文字。Flutter 會以最少的更改來響應使用者輸入。
在本教程中,將您識別的每個元素都編寫成自己的 Widget。
建立應用基礎程式碼
#在本節中,列出基本的 Flutter 應用程式碼以啟動您的應用。
-
將
lib/main.dart的內容替換為以下程式碼。此應用使用一個引數來表示應用標題和應用appBar上顯示的標題。此決定簡化了程式碼。dartimport 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { const String appTitle = 'Flutter layout demo'; return MaterialApp( title: appTitle, home: Scaffold( appBar: AppBar(title: const Text(appTitle)), body: const Center( child: Text('Hello World'), ), ), ); } }
新增標題部分
#在本節中,建立一個類似於以下佈局的 TitleSection Widget。
標題部分草圖和原型 UI
新增 TitleSection Widget
#
在 MyApp 類之後新增以下程式碼。
class TitleSection extends StatelessWidget {
const TitleSection({super.key, required this.name, required this.location});
final String name;
final String location;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(32),
child: Row(
children: [
Expanded(
/*1*/
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/*2*/
Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Text(
name,
style: const TextStyle(fontWeight: FontWeight.bold),
),
),
Text(location, style: TextStyle(color: Colors.grey[500])),
],
),
),
/*3*/
Icon(Icons.star, color: Colors.red[500]),
const Text('41'),
],
),
);
}
}
- 要使用行中的所有剩餘可用空間,請使用
ExpandedWidget 擴充套件ColumnWidget。要將列放置在行的開頭,請將crossAxisAlignment屬性設定為CrossAxisAlignment.start。 - 要在文字行之間新增間距,請將這些行放在
PaddingWidget 中。 - 標題行以紅色星形圖示和文字
41結尾。整個行都位於PaddingWidget 內部,並且每個邊緣都填充 32 畫素。
將應用主體更改為可滾動檢視
#在 body 屬性中,將 Center Widget 替換為 SingleChildScrollView Widget。在 SingleChildScrollView Widget 內部,將 Text Widget 替換為 Column Widget。
body: const Center(
child: Text('Hello World'),
body: const SingleChildScrollView(
child: Column(
children: [
這些程式碼更新會以以下方式更改應用。
SingleChildScrollViewWidget 可以滾動。這允許不適合當前螢幕的元素顯示。ColumnWidget 以children屬性中列出的順序顯示任何元素。children列表中列出的第一個元素顯示在列表的頂部。children列表中的元素按陣列順序從上到下在螢幕上顯示。
更新應用以顯示標題部分
#將 TitleSection Widget 作為 children 列表中的第一個元素。這將將其放置在螢幕的頂部。將提供的名稱和位置傳遞給 TitleSection 建構函式。
children: [
TitleSection(
name: 'Oeschinen Lake Campground',
location: 'Kandersteg, Switzerland',
),
],
新增按鈕部分
#在本節中,新增將為您的應用新增功能的按鈕。
按鈕 部分包含三個列,它們使用相同的佈局:圖示在文字上方。
按鈕部分草圖和原型 UI
計劃將這些列分佈在一行中,以便每個列佔用相同的空間。用主色將所有文字和圖示塗色。
新增 ButtonSection Widget
#
將以下程式碼新增到 TitleSection Widget 之後,以包含構建按鈕行的程式碼。
class ButtonSection extends StatelessWidget {
const ButtonSection({super.key});
@override
Widget build(BuildContext context) {
final Color color = Theme.of(context).primaryColor;
// ···
}
}
建立一個用於製作按鈕的 Widget
#由於每個列的程式碼可以使用相同的語法,因此建立一個名為 ButtonWithText 的 Widget。該 Widget 的建構函式接受顏色、圖示資料和按鈕標籤。使用這些值,該 Widget 構建一個帶有 Icon 和樣式化 Text Widget 作為子項的 Column。為了分隔這些子項,將 Text Widget 包裝在一個 Padding Widget 中。
在 ButtonSection 類之後新增以下程式碼。
class ButtonSection extends StatelessWidget {
const ButtonSection({super.key});
// ···
}
class ButtonWithText extends StatelessWidget {
const ButtonWithText({
super.key,
required this.color,
required this.icon,
required this.label,
});
final Color color;
final IconData icon;
final String label;
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: color),
Padding(
padding: const EdgeInsets.only(top: 8),
child: Text(
label,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
color: color,
),
),
),
],
);
}
}
使用 Row Widget 定位按鈕
#
將以下程式碼新增到 ButtonSection Widget 中。
- 為每個按鈕新增三個
ButtonWithTextWidget 的例項。 - 傳遞該特定按鈕的顏色、
Icon和文字。 - 使用
MainAxisAlignment.spaceEvenly值沿主軸對齊列。RowWidget 的主軸是水平的,而ColumnWidget 的主軸是垂直的。因此,此值告訴 Flutter 以相等數量在每個列之前、之間和之後排列空閒空間。
class ButtonSection extends StatelessWidget {
const ButtonSection({super.key});
@override
Widget build(BuildContext context) {
final Color color = Theme.of(context).primaryColor;
return SizedBox(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ButtonWithText(color: color, icon: Icons.call, label: 'CALL'),
ButtonWithText(color: color, icon: Icons.near_me, label: 'ROUTE'),
ButtonWithText(color: color, icon: Icons.share, label: 'SHARE'),
],
),
);
}
}
class ButtonWithText extends StatelessWidget {
const ButtonWithText({
super.key,
required this.color,
required this.icon,
required this.label,
});
final Color color;
final IconData icon;
final String label;
@override
Widget build(BuildContext context) {
return Column(
// ···
);
}
}
更新應用以顯示按鈕部分
#將按鈕部分新增到 children 列表中。
TitleSection(
name: 'Oeschinen Lake Campground',
location: 'Kandersteg, Switzerland',
),
ButtonSection(),
],
新增文字部分
#在本節中,新增文字描述以完成您的佈局。
文字塊草圖和原型 UI
新增 TextSection Widget
#
將以下程式碼作為單獨的 Widget 新增到 ButtonSection Widget 之後。
class TextSection extends StatelessWidget {
const TextSection({super.key, required this.description});
final String description;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(32),
child: Text(description, softWrap: true),
);
}
}
透過將 softWrap 設定為 true,文字行將填充列寬,然後在單詞邊界處換行。
更新應用以顯示文字部分
#將新的 TextSection Widget 作為 ButtonSection 之後的子項新增。在新增 TextSection Widget 時,將 description 屬性設定為位置描述的文字。
location: 'Kandersteg, Switzerland',
),
ButtonSection(),
TextSection(
description:
'Lake Oeschinen lies at the foot of the Blüemlisalp in the '
'Bernese Alps. Situated 1,578 meters above sea level, it '
'is one of the larger Alpine Lakes. A gondola ride from '
'Kandersteg, followed by a half-hour walk through pastures '
'and pine forest, leads you to the lake, which warms to 20 '
'degrees Celsius in the summer. Activities enjoyed here '
'include rowing, and riding the summer toboggan run.',
),
],
新增影像部分
#在本節中,新增影像檔案以完成您的佈局。
配置應用以使用提供的影像
#要配置應用以引用影像,請修改其 pubspec.yaml 檔案。
在專案頂部建立一個
images目錄。-
下載
lake.jpg影像並將其新增到新的images目錄。 -
要包含影像,請將
assets標籤新增到應用根目錄中的pubspec.yaml檔案中。當您新增assets時,它充當指向程式碼中可用影像的指標集。pubspec.yamlyamlflutter: uses-material-design: true assets: - images/lake.jpg
建立 ImageSection Widget
#
在其他宣告之後定義以下 ImageSection Widget。
class ImageSection extends StatelessWidget {
const ImageSection({super.key, required this.image});
final String image;
@override
Widget build(BuildContext context) {
return Image.asset(image, width: 600, height: 240, fit: BoxFit.cover);
}
}
BoxFit.cover 值告訴 Flutter 使用兩個約束來顯示影像。首先,以儘可能小的尺寸顯示影像。其次,覆蓋佈局分配的所有空間,稱為渲染框。
更新應用以顯示影像部分
#將 ImageSection Widget 作為 children 列表中的第一個子項新增。將 image 屬性設定為您在 配置應用以使用提供的影像 中新增的影像的路徑。
children: [
ImageSection(
image: 'images/lake.jpg',
),
TitleSection(
name: 'Oeschinen Lake Campground',
location: 'Kandersteg, Switzerland',
恭喜
#就是這樣!當您熱過載應用時,您的應用應該如下所示。
完成的應用
資源
#您可以從以下位置訪問本教程中使用的資源
Dart 程式碼: main.dart
圖片: ch-photo
Pubspec: pubspec.yaml
下一步
#要為這個佈局新增互動性,請參考 互動教程。