建立 Flutter 版面配置
本教學說明如何在 Flutter 中設計和建立版面配置。
如果您使用提供的範例程式碼,您可以建立下列應用程式。
照片由 Dino Reichmuth 拍攝,來自 Unsplash。文字出自 瑞士觀光局。
若要更了解版面配置機制,請從 Flutter 的版面配置方法開始。
繪製版面配置圖
#在本節中,請考量您希望應用程式使用者獲得什麼樣的使用者體驗。
考量如何定位您的使用者介面元件。版面配置是由這些定位的總體最終結果所組成。考慮規劃您的版面配置,以加快您的編碼速度。使用視覺提示來了解螢幕上物件的放置位置會很有幫助。
使用您偏好的任何方法,例如介面設計工具或鉛筆和紙張。在編寫程式碼之前,請找出您想要在螢幕上放置元素的位置。這就像是程式設計版本的格言:「三思而後行」。
提出這些問題,將版面配置分解為基本元素。
- 您能識別出列和欄嗎?
- 版面配置是否包含格線?
- 是否有重疊的元素?
- UI 是否需要索引標籤?
- 您需要對齊、填補或加框哪些項目?
識別較大的元素。在此範例中,您將圖片、標題、按鈕和說明排列成一欄。
繪製每一列的圖表。
第 1 列,即標題區塊,有三個子項:一個文字欄、一個星形圖示和一個數字。其第一個子項,即欄,包含兩行文字。第一個欄可能需要更多空間。
第 2 列,即按鈕區塊,有三個子項:每個子項都包含一個欄,其中接著包含一個圖示和文字。
在繪製版面配置圖之後,請考量如何編寫程式碼。
您會在一個類別中編寫所有程式碼嗎?或者,您會為版面配置的每個部分建立一個類別嗎?
為了遵循 Flutter 的最佳實務,請建立一個類別或 Widget,以包含您版面配置的每個部分。當 Flutter 需要重新繪製 UI 的一部分時,它會更新變更的最小部分。這就是為什麼 Flutter 將「所有事物都視為 widget」的原因。如果只有 Text
widget 中的文字變更,則 Flutter 只會重新繪製該文字。Flutter 會盡可能變更最少的 UI 以回應使用者輸入。
在本教學中,將您識別的每個元素撰寫為自己的 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。
加入 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'),
],
),
);
}
}
- 若要使用列中所有剩餘的可用空間,請使用
Expanded
widget 來延伸Column
widget。若要將欄放置在列的開頭,請將crossAxisAlignment
屬性設定為CrossAxisAlignment.start
。 - 若要在文字列之間加入間距,請將這些列放在
Padding
widget 中。 - 標題列以紅色星形圖示和文字
41
結束。整個列位於Padding
widget 內,並將每個邊緣填補 32 個像素。
將應用程式主體變更為可捲動的檢視
#在 body
屬性中,以 SingleChildScrollView
widget 取代 Center
widget。在 SingleChildScrollView
widget 內,以 Column
widget 取代 Text
widget。
body: const Center(
child: Text('Hello World'),
body: const SingleChildScrollView(
child: Column(
children: [
這些程式碼更新會以以下方式變更應用程式。
SingleChildScrollView
widget 可以捲動。這允許顯示不符合目前螢幕的元素。Column
widget 會依列出的順序顯示其children
屬性中的任何元素。children
清單中列出的第一個元素會顯示在清單頂端。children
清單中的元素會依照陣列順序從上到下顯示在螢幕上。
更新應用程式以顯示標題區塊
#將 TitleSection
widget 加入為 children
清單中的第一個元素。這會將其放置在螢幕頂端。將提供的名稱和位置傳遞至 TitleSection
建構函式。
children: [
TitleSection(
name: 'Oeschinen Lake Campground',
location: 'Kandersteg, Switzerland',
),
],
加入按鈕區塊
#在本節中,加入將會為您的應用程式新增功能的按鈕。
按鈕區塊包含三個使用相同版面配置的欄:一個圖示在文字列上方。
規劃將這些欄分散在一列中,讓每一欄都佔用相同的空間。使用主要色彩繪製所有文字和圖示。
加入 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
。為了幫助區隔這些子項,Padding
widget 會使用 Padding
widget 包裝 Text
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 中。
- 為每個按鈕加入三個
ButtonWithText
widget 執行個體。 - 傳遞該特定按鈕的色彩、
Icon
和文字。 - 使用
MainAxisAlignment.spaceEvenly
值對齊主軸的欄。Row
widget 的主軸是水平的,而Column
widget 的主軸是垂直的。然後,此值會告知 Flutter 在沿著Row
的每個欄之前、之間和之後以相等數量排列可用空間。
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(),
],
加入文字區塊
#在本節中,將文字說明加入到此應用程式。
加入 TextSection
widget
#在 ButtonSection
widget 之後,以單獨的 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
目錄。若要包含影像,請在應用程式的根目錄的
pubspec.yaml
檔案中加入assets
標籤。當您加入assets
時,它會作為程式碼可使用的影像指標集。pubspec.yamlyamlflutter: uses-material-design: true assets: - images/lake.jpg
建立 ImageSection
小工具
#在其他宣告之後,定義以下 ImageSection
小工具。
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 以兩個約束條件顯示圖片。首先,盡可能以最小尺寸顯示圖片。其次,覆蓋配置所分配的所有空間,稱為渲染框。
更新應用程式以顯示圖片區塊
#在 children
列表中新增一個 ImageSection
小工具作為第一個子元素。將 image
屬性設定為您在 設定您的應用程式以使用提供的圖片 中新增的圖片路徑。
children: [
ImageSection(
image: 'images/lake.jpg',
),
TitleSection(
name: 'Oeschinen Lake Campground',
location: 'Kandersteg, Switzerland',
恭喜
#就是這樣!當您熱重載應用程式時,您的應用程式應該看起來像這樣。
資源
#您可以從以下位置存取本教學中使用的資源
Dart 程式碼: main.dart
圖片: ch-photo
Pubspec: pubspec.yaml
下一步
#若要為此佈局新增互動性,請依照 互動性教學。
除非另有說明,否則本網站上的文件反映了 Flutter 的最新穩定版本。頁面最後更新於 2024-09-26。 檢視原始碼 或 回報問題。