Flutter 中的版面配置
概覽
#Flutter 版面配置機制的核心就是元件(Widgets)。 在 Flutter 中,幾乎所有東西都是元件(Widget)—甚至 版面配置模型本身也是元件(Widget)。你在 Flutter 應用程式中看到的 圖片、圖示和文字,都是元件(Widget)。 但你看不到的東西也是元件(Widget), 像是用來排列、限制和對齊可見元件(Widget)的 行(row)、列(column)和網格(grid)。 你可以透過組合元件(Widgets)來建立更複雜的元件(Widgets), 進而完成版面配置。
概念範例
#在以下範例中,第一張螢幕截圖顯示了三個帶有標籤的圖示, 而第二張螢幕截圖則包含了行與列的視覺化版面配置。 在第二張螢幕截圖中,debugPaintSizeEnabled 被設為 true, 讓你可以看到版面配置的視覺化效果。
以下是前述範例的元件樹(widget tree)圖示:

大部分內容應該如你所預期,但你可能會對那些(粉紅色顯示的)容器感到好奇。Container 是一個元件(Widget)類別, 它允許你自訂其子元件(child widget)。當你想要為子元件加入 內距(padding)、外距(margin)、邊框(border)或背景顏色等效果時, 就可以使用 Container。
每個 Text 元件都被放在 Container 中,以加入外距。整個 Row 也被放在 Container 中,以在該行周圍加入內距。
其餘的 UI 則由屬性控制。 你可以透過 Icon 的 color 屬性來設定顏色。 使用 Text.style 屬性可以設定字型、顏色、字重等。 列(columns)與行(rows)則有屬性可讓你指定其子元件 如何垂直或水平對齊,以及子元件應該佔用多少空間。
排版單一元件(Widget)
#在 Flutter 中,如何排版一個單一元件(Widget)? 本節將示範如何建立並顯示一個簡單的元件(Widget), 同時也會展示一個簡單 Hello World 應用程式的完整程式碼。
在 Flutter 中,只需幾個步驟即可將文字、圖示或圖片顯示在螢幕上。
1. 選擇一個版面配置元件(Layout Widget)
#你可以根據想要如何對齊或限制可見元件(Widget), 從多種版面配置元件(Layout widgets)中選擇, 因為這些特性通常會傳遞給其所包含的元件。
例如,你可以使用 Center 版面配置元件(Layout Widget), 將可見元件(Widget)在水平與垂直方向上置中顯示:
Center(
// Content to be centered here.
)2. 建立可見元件 (Widget)
#為你的應用程式選擇一個可見元件 (Widget)來承載可見元素,例如文字、圖片或圖示。
例如,你可以使用Text元件來顯示一些文字:
Text('Hello World')3. 將可見元件 (Widget) 加入版面配置元件 (Layout Widget)
#所有版面配置元件 (Layout widgets) 都有以下其中之一:
- 如果只接收單一子元件,則有
child屬性,例如Center或Container - 如果接收元件清單,則有
children屬性,例如Row、Column、ListView或Stack。
將 Text 元件加入 Center 元件中:
const Center(
child: Text('Hello World'),
),4. 將版面配置元件 (Layout Widget) 加入頁面
#一個 Flutter 應用程式本身就是一個元件(Widget),而大多數元件都有一個 build() 方法。在應用程式的 build() 方法中實例化並回傳一個元件,便會顯示該元件。
對於一般應用程式,你可以將 Container 元件(Widget)加入應用程式的 build() 方法中:
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return Container(
decoration: const BoxDecoration(color: Colors.white),
child: const Center(
child: Text(
'Hello World',
textDirection: TextDirection.ltr,
style: TextStyle(fontSize: 32, color: Colors.black87),
),
),
);
}
}預設情況下,一般應用程式(general app)不會包含AppBar、標題或背景顏色。如果你希望在一般應用程式中加入這些功能,則需要自行實作。本範例應用程式將背景顏色設為白色,文字設為深灰色,以模仿 Material 應用程式的風格。
對於Material應用程式,你可以使用Scaffold元件(Widget);它會提供預設橫幅、背景顏色,並且有 API 可用於新增抽屜(drawer)、訊息條(snack bar)以及底部彈窗(bottom sheet)。接著,你可以將Center元件直接加到body屬性,作為首頁內容。
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'),
),
),
);
}
}若要建立 Cupertino 應用程式,請使用 CupertinoApp 與 CupertinoPageScaffold 元件(Widgets)。
與 Material 不同,這裡不會自動提供預設的橫幅或背景顏色,你需要自行設定。
若要設定預設顏色,請將已設定的
CupertinoThemeData傳入應用程式的theme屬性。若要在應用程式頂部加入 iOS 風格的導覽列,請在 scaffold 的
navigationBar屬性中加入CupertinoNavigationBar元件。你可以使用CupertinoColors提供的顏色來設定元件,使其符合 iOS 設計風格。若要配置應用程式的主體內容,請將 scaffold 的
child屬性設為你想要的元件,例如Center或Column。
想了解還有哪些 UI 元件可以加入,請參考 Cupertino 函式庫。
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const CupertinoApp(
title: 'Flutter layout demo',
theme: CupertinoThemeData(
brightness: Brightness.light,
primaryColor: CupertinoColors.systemBlue,
),
home: CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
backgroundColor: CupertinoColors.systemGrey,
middle: Text('Flutter layout demo'),
),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [Text('Hello World')],
),
),
),
);
}
}5. 執行你的應用程式
#
垂直與水平排列多個元件(Widgets)
#最常見的版面配置模式之一,就是將元件(Widgets) 垂直或水平排列。你可以使用 Row 元件來水平排列元件, 使用 Column 元件來垂直排列元件。
要在 Flutter 中建立 row 或 column,只需將一個子元件(Widgets)清單 加入 Row 或 Column 元件。每個子元件本身 也可以是 row 或 column,如此類推。 以下範例展示了如何在 row 或 column 內巢狀放置 row 或 column。
這個版面配置以 Row 為主體。該 row 包含兩個子元件: 左側是一個 column,右側是一張圖片:

左側 column 的元件樹中巢狀了多個 row 與 column。

你將在 巢狀 row 與 column 中實作 Pavlova 的部分版面配置程式碼。
對齊元件(Widgets)
#你可以透過 mainAxisAlignment 與 crossAxisAlignment 屬性來控制 row 或 column 如何對齊其子元件。 對於 row,主軸(main axis)為水平方向,交叉軸(cross axis)為垂直方向。 對於 column,主軸為垂直方向,交叉軸為水平方向。


MainAxisAlignment 與 CrossAxisAlignment 列舉型別(enum)提供多種常數,可用來控制對齊方式。
在下列範例中,每張圖片寬度皆為 100 像素。 渲染區域(本例為整個螢幕)寬度超過 300 像素,因此將主軸對齊設為 spaceEvenly 會讓多餘的水平空間平均分配在每張圖片的前後與之間。
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Image.asset('images/pic1.jpg'),
Image.asset('images/pic2.jpg'),
Image.asset('images/pic3.jpg'),
],
);
App source: row_column
Column(欄)與 Row(列)的運作方式相同。以下範例展示了一個包含 3 張圖片的 column,每張圖片高度為 100 像素。Render box(渲染區塊)的高度(在此例中為整個螢幕)大於 300 像素,因此將主軸對齊方式設為 spaceEvenly 時,會將多餘的垂直空間平均分配在每張圖片之間、上方與下方。
Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Image.asset('images/pic1.jpg'),
Image.asset('images/pic2.jpg'),
Image.asset('images/pic3.jpg'),
],
);
應用程式原始碼: row_column
元件尺寸調整
#當版面配置過大,無法完全顯示於裝置螢幕時,受影響的邊緣會出現黃黑相間的條紋圖案。 以下是一個row 過寬的範例:

可以透過使用 Expanded 元件,讓元件尺寸適合在 row 或 column 內顯示。若要修正前述圖片 row 超出其 render box 的情況, 請將每個圖片包裹在 Expanded 元件中。
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(child: Image.asset('images/pic1.jpg')),
Expanded(child: Image.asset('images/pic2.jpg')),
Expanded(child: Image.asset('images/pic3.jpg')),
],
);
應用程式原始碼: sizing
有時你可能希望某個元件(Widget)佔據比其他同列元件多兩倍的空間。此時,可以使用 Expanded 元件的 flex 屬性,這是一個整數,用來決定該元件的彈性係數(flex factor)。預設的彈性係數為 1。以下程式碼將中間圖片的彈性係數設為 2:
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(child: Image.asset('images/pic1.jpg')),
Expanded(flex: 2, child: Image.asset('images/pic2.jpg')),
Expanded(child: Image.asset('images/pic3.jpg')),
],
);
應用程式原始碼: sizing
元件(Widgets)打包(Packing)
#預設情況下,row 或 column 會沿著其主軸(main axis)盡可能佔據空間,但如果你希望讓子元件(children)緊密排列,可以將其 mainAxisSize 設為 MainAxisSize.min。以下範例使用此屬性,將星形圖示元件(icon widgets)緊密排列在一起。
Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.star, color: Colors.green[500]),
Icon(Icons.star, color: Colors.green[500]),
Icon(Icons.star, color: Colors.green[500]),
const Icon(Icons.star, color: Colors.black),
const Icon(Icons.star, color: Colors.black),
],
)
應用程式原始碼: pavlova
巢狀的 row 與 column
#版面配置框架允許你在 row 與 column 之中,依需求任意深度地巢狀 row 與 column。 讓我們來看看下方版面配置中紅框標示區塊的程式碼:

紅框標示的區塊是由兩個 row 實作而成。評分 row 包含五顆星星與評論數量。圖示 row 則包含三個由圖示與文字組成的 column。
評分 row 的元件樹(widget tree)如下:

ratings 變數建立了一個 row,其中包含一個較小的 5 星圖示 row,以及文字:
final stars = Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.star, color: Colors.green[500]),
Icon(Icons.star, color: Colors.green[500]),
Icon(Icons.star, color: Colors.green[500]),
const Icon(Icons.star, color: Colors.black),
const Icon(Icons.star, color: Colors.black),
],
);
final ratings = Container(
padding: const EdgeInsets.all(20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
stars,
const Text(
'170 Reviews',
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w800,
fontFamily: 'Roboto',
letterSpacing: 0.5,
fontSize: 20,
),
),
],
),
);在評分列(ratings row)下方的圖示列(icons row)包含 3 個欄位,每個欄位內都有一個圖示和兩行文字,如下圖的元件樹(widget tree)所示:
iconList 變數定義了圖示列(icons row):
const descTextStyle = TextStyle(
color: Colors.black,
fontWeight: FontWeight.w800,
fontFamily: 'Roboto',
letterSpacing: 0.5,
fontSize: 18,
height: 2,
);
// DefaultTextStyle.merge() allows you to create a default text
// style that is inherited by its child and all subsequent children.
final iconList = DefaultTextStyle.merge(
style: descTextStyle,
child: Container(
padding: const EdgeInsets.all(20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Column(
children: [
Icon(Icons.kitchen, color: Colors.green[500]),
const Text('PREP:'),
const Text('25 min'),
],
),
Column(
children: [
Icon(Icons.timer, color: Colors.green[500]),
const Text('COOK:'),
const Text('1 hr'),
],
),
Column(
children: [
Icon(Icons.restaurant, color: Colors.green[500]),
const Text('FEEDS:'),
const Text('4-6'),
],
),
],
),
),
);leftColumn 變數包含評分與圖示列,以及描述 Pavlova 的標題和文字:
final leftColumn = Container(
padding: const EdgeInsets.fromLTRB(20, 30, 20, 20),
child: Column(children: [titleText, subTitle, ratings, iconList]),
);左側欄位被放置在SizedBox中,以限制其寬度。 最後,整個 UI 是將整行(包含左側欄位和圖片)放在Card內建構而成。
Pavlova 圖片來自 Pixabay。 你可以使用Image.network()從網路嵌入圖片,但在本範例中,圖片被儲存於專案的 images 目錄,並新增至 pubspec file,然後透過Images.asset()來存取。 如需更多資訊,請參閱新增資源與圖片。
body: Center(
child: Container(
margin: const EdgeInsets.fromLTRB(0, 40, 0, 30),
height: 600,
child: Card(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(width: 440, child: leftColumn),
mainImage,
],
),
),
),
),App 原始碼: pavlova
常用版面配置元件 (Layout widgets)
#Flutter 擁有豐富的版面配置元件 (Layout widgets) 函式庫。以下介紹幾個最常用的元件,目的是讓你能快速上手,而不是給你一份完整清單。若需查詢其他可用元件,請參考 Widget catalog,或在 API 文件 的搜尋框中查找。此外,API 文件中的元件頁面通常也會建議其他可能更適合你需求的相似元件。
以下元件分為兩類:來自 widgets library 的標準元件,以及來自 Material library 的專用元件。任何應用程式都可以使用 widgets library,但只有 Material 應用程式可以使用 Material Components library。
CupertinoPageScaffold- 提供 iOS 風格頁面的基本版面結構。
CupertinoNavigationBar- 在螢幕頂部建立 iOS 風格的導覽列。
CupertinoSegmentedControl- 建立 iOS 風格的分段控制元件,用於選擇。
CupertinoTabBar和CupertinoTabScaffold- 建立 iOS 典型的底部分頁列。
Container
#許多版面配置會大量使用 Container 來透過內距(padding)分隔元件,或是增加邊框與外距(margin)。你可以將整個版面包在一個 Container 中,並更改其背景顏色或圖片,以改變裝置背景。
摘要(Container)
#- 增加內距(padding)、外距(margin)、邊框
- 更改背景顏色或圖片
- 僅包含一個子元件(child),但該子元件可以是
Row、Column,甚至是元件樹的根節點

範例(Container)
#此版面由一個包含兩列的 column 組成,每一列各有兩張圖片。利用 Container 將 column 的背景色設為較淺的灰色。
Widget _buildImageColumn() {
return Container(
decoration: const BoxDecoration(color: Colors.black26),
child: Column(children: [_buildImageRow(1), _buildImageRow(3)]),
);
}
Container 也用來為每張圖片新增圓角邊框和邊距:
Widget _buildDecoratedImage(int imageIndex) => Expanded(
child: Container(
decoration: BoxDecoration(
border: Border.all(width: 10, color: Colors.black38),
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
margin: const EdgeInsets.all(4),
child: Image.asset('images/pic$imageIndex.jpg'),
),
);
Widget _buildImageRow(int imageIndex) => Row(
children: [
_buildDecoratedImage(imageIndex),
_buildDecoratedImage(imageIndex + 1),
],
);你可以在 教學課程 中找到更多 Container 範例。
應用程式原始碼: container
GridView
#使用 GridView 將元件(Widgets)以二維清單方式排列。GridView 提供兩種預設的清單,或是你也可以自訂自己的格線。當 GridView 偵測到內容過長無法完全顯示於 render box 時,會自動產生捲動功能。
摘要(GridView)
#- 以格線方式排列元件(Widgets)
- 偵測當欄內容超出 render box 時,自動提供捲動功能
- 你可以自訂自己的格線,或使用以下其中一種預設格線:
GridView.count可讓你指定欄位數量GridView.extent可讓你指定每個格子(tile)的最大像素寬度
範例(GridView)
#
使用 GridView.count 建立一個在直向模式下有 2 欄、橫向模式下有 3 欄的格線。標題是透過為每個 GridTile 設定 footer 屬性產生。
Dart 程式碼: grid_list_demo.dart
Widget _buildGrid() => GridView.extent(
maxCrossAxisExtent: 150,
padding: const EdgeInsets.all(4),
mainAxisSpacing: 4,
crossAxisSpacing: 4,
children: _buildGridTileList(30),
);
// The images are saved with names pic0.jpg, pic1.jpg...pic29.jpg.
// The List.generate() constructor allows an easy way to create
// a list when objects have a predictable naming pattern.
List<Widget> _buildGridTileList(int count) =>
List.generate(count, (i) => Image.asset('images/pic$i.jpg'));ListView
#ListView 是一個類似直欄的元件(Widget),當內容超出其繪製區域時,會自動提供滾動功能。
摘要(ListView)
#- 一個專門用於組織方塊清單的
Column - 可以橫向或縱向排列
- 能偵測內容無法容納時自動提供滾動
- 可配置性較
Column低,但更易於使用且支援滾動
範例(ListView)
#Widget _buildList() {
return ListView(
children: [
_tile('CineArts at the Empire', '85 W Portal Ave', Icons.theaters),
_tile('The Castro Theater', '429 Castro St', Icons.theaters),
_tile('Alamo Drafthouse Cinema', '2550 Mission St', Icons.theaters),
_tile('Roxie Theater', '3117 16th St', Icons.theaters),
_tile(
'United Artists Stonestown Twin',
'501 Buckingham Way',
Icons.theaters,
),
_tile('AMC Metreon 16', '135 4th St #3000', Icons.theaters),
const Divider(),
_tile('K\'s Kitchen', '757 Monterey Blvd', Icons.restaurant),
_tile('Emmy\'s Restaurant', '1923 Ocean Ave', Icons.restaurant),
_tile('Chaiya Thai Restaurant', '272 Claremont Blvd', Icons.restaurant),
_tile('La Ciccia', '291 30th St', Icons.restaurant),
],
);
}
ListTile _tile(String title, String subtitle, IconData icon) {
return ListTile(
title: Text(
title,
style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 20),
),
subtitle: Text(subtitle),
leading: Icon(icon, color: Colors.blue[500]),
);
}Stack
#使用 Stack 來將元件(Widgets)堆疊在一個基礎元件之上——通常是一張圖片。這些元件可以完全或部分地重疊在基礎元件上。
摘要(Stack)
#- 適用於需要重疊在其他元件上的元件
children清單中的第一個元件是基礎元件;後續的子元件會疊加在該基礎元件之上Stack的內容無法滾動- 你可以選擇裁剪超出 render box 的子元件
範例(Stack)
#
使用 Stack 疊加一個 Container(其會在半透明黑色背景上顯示其 Text)於 CircleAvatar 之上。Stack 透過 alignment 屬性與 Alignment 來偏移文字。
App 原始碼: card_and_stack
Widget _buildStack() {
return Stack(
alignment: const Alignment(0.6, 0.6),
children: [
const CircleAvatar(
backgroundImage: AssetImage('images/pic.jpg'),
radius: 100,
),
Container(
decoration: const BoxDecoration(color: Colors.black45),
child: const Text(
'Mia B',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
],
);
}Card
#一個 Card,來自 Material library, 用於承載相關的資訊片段,可以由幾乎任何元件(Widget)組成,但通常會與 ListTile 一起使用。Card 僅接受一個子元件, 但其子元件可以是 column、row、list、grid, 或其他支援多個子元件的元件。 預設情況下,Card 會將自身尺寸縮小為 0 x 0 像素。 你可以使用 SizedBox 來限制 card 的尺寸。
在 Flutter 中,Card 具有略為圓角的邊緣 以及投影陰影,帶來 3D 效果。 變更 Card 的 elevation 屬性可以控制 投影陰影的效果。例如,將 elevation 設為 24, 會讓 Card 視覺上從表面抬升得更高,且陰影會更加分散。 支援的 elevation 值請參考 Elevation 於 Material guidelines。 若指定不支援的值,則會完全關閉投影陰影。
摘要(Card)
#- 實作 Material card
- 用於呈現相關的資訊片段
- 僅接受一個子元件,但該子元件可以是
Row、Column,或其他可包含多個子元件的元件 - 以圓角與投影陰影顯示
Card的內容無法滾動- 來自 Material library
範例(Card)
#Widget _buildCard() {
return SizedBox(
height: 210,
child: Card(
child: Column(
children: [
ListTile(
title: const Text(
'1625 Main Street',
style: TextStyle(fontWeight: FontWeight.w500),
),
subtitle: const Text('My City, CA 99984'),
leading: Icon(Icons.restaurant_menu, color: Colors.blue[500]),
),
const Divider(),
ListTile(
title: const Text(
'(408) 555-1212',
style: TextStyle(fontWeight: FontWeight.w500),
),
leading: Icon(Icons.contact_phone, color: Colors.blue[500]),
),
ListTile(
title: const Text('costa@example.com'),
leading: Icon(Icons.contact_mail, color: Colors.blue[500]),
),
],
),
),
);
}ListTile
#使用 ListTile,這是一個來自 Material library 的專用 row 元件(Widget),可以輕鬆建立一個 row,內含最多 3 行文字,並可選擇性加入前置(leading)及後置(trailing)圖示。ListTile 最常用於 Card 或 ListView,但也可用於其他地方。
摘要(ListTile)
#- 一個專用的 row,可包含最多 3 行文字及可選的圖示
- 可設定性比
Row少,但更容易使用 - 來自 Material library
範例(ListTile)
#限制條件(Constraints)
#若要完全理解 Flutter 的版面配置系統,你需要學習 Flutter 如何在版面中定位與調整元件(components)的大小。更多資訊請參閱 Understanding constraints。
影片
#以下影片為 Flutter in Focus 系列的一部分,說明 Stateless 與 Stateful 元件(Widgets)。
Watch on YouTube in a new tab: "How to create stateless widgets"
Watch on YouTube in a new tab: "How and when stateful widgets are best used"
每一集 Widget of the Week series 都會聚焦介紹一個元件(Widget),其中有多集包含版面配置元件(Layout widgets)。
Watch on YouTube in a new tab: "Introducing widget of the week"
Flutter Widget of the Week 播放清單
其他資源
#以下資源在撰寫版面配置程式碼時可能有所幫助。
- Layout tutorial
- 學習如何建立版面配置。
- Widget catalog
- 介紹 Flutter 提供的多種元件(Widgets)。
- HTML/CSS Analogs in Flutter
- 若你熟悉網頁程式設計,本頁將 HTML/CSS 的功能對應到 Flutter 的特性。
- API reference docs
- 提供所有 Flutter 函式庫的參考文件。
- Adding assets and images
- 說明如何將圖片及其他資源加入你的應用程式套件中。
- Zero to One with Flutter
- 一位開發者撰寫其第一個 Flutter 應用程式的經驗分享。






