Flutter 中的佈局
Flutter 佈局的核心機制是 widgets。在 Flutter 中,幾乎所有東西都是 widget —— 甚至佈局模型都是 widgets。你在 Flutter 應用程式中看到的影象,圖示和文字都是 widgets。此外不能直接看到的也是 widgets,例如用來排列、限制和對齊可見 widgets 的行、列和網格。
你可以透過組合 widgets 來建構更復雜的 widgets 來建立佈局。比如,下面第一個截圖上有 3 個圖示,每個圖示下面都有一個標籤:


第二個截圖顯示了可視佈局,可以看到有一排三列,其中每列包含一個圖示和一個標籤。
以下是這個 UI 的 widget 樹形圖:
圖上大部分應該和你預想的一樣,但你可能會疑惑 containers(圖上粉色顯示的)是什麼。
Container
是一個 widget,允許你自訂其子 widget。舉幾個例子,如果要新增 padding、margin、邊框或背景顏色,你就可以用上 Container
了。
在這個例子中,每個 Text
widget 都被放在一個 Container
以新增 padding。整個 Row
也被放在一個 Container
中,以便新增 padding。
這個例子其餘部分的 UI 由屬性控制。透過 Icon
的 color
屬性來設定它的顏色,透過 Text.style
屬性來設定文字的字型、顏色、字重等等。列和行有一些屬性可以讓你指定子項垂直或水平的對齊方式以及子項應占用的空間大小。
佈局 widget
如何在 Flutter 中佈局單個 widget?本節將介紹如何建立和顯示單個 widget。本節還包括一個簡單的 Hello World app 的完整程式碼。
在 Flutter 中,只需幾步就可以在螢幕上顯示文字、圖示或影象。
1. 選擇一個佈局 widget
根據你想要對齊或限制可見 widget 的方式從各種 layout widgets 中進行選擇,因為這些特性通常會傳遞它所給包含的 widget。
本例使用將其內容水平和垂直居中的 Center
。
2. 建立一個可見 widget
舉個例子,建立一個 Text
widget:
Text('Hello World'),
建立一個 Image
widget:
Image.asset(
'images/lake.jpg',
fit: BoxFit.cover,
),
建立一個 Icon
widget:
Icon(
Icons.star,
color: Colors.red[500],
),
3. 將可見 widget 新增到佈局 widget
所有佈局 widgets 都具有以下任一項:
-
一個
child
屬性,如果它們只包含一個子項 —— 例如Center
和Container
-
一個
children
屬性,如果它們包含多個子項 —— 例如Row
、Column
、ListView
和Stack
將 Text
widget 新增進 Center
widget:
const Center(
child: Text('Hello World'),
),
4. 將佈局 widget 新增到頁面
一個 Flutter app 本身就是一個 widget,大多數 widgets 都有一個 build()
方法,在 app 的 build()
方法中例項化和返回一個 widget 會讓它顯示出來。
對於 Material
app,你可以使用 Scaffold
widget,它提供預設的 banner 背景顏色,還有用於新增抽屜、提示條和底部列表彈窗的 API。你可以將 Center
widget 直接新增到主頁 body
的屬性中。
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter layout demo',
home: Scaffold(
appBar: AppBar(
title: const Text('Flutter layout demo'),
),
body: const Center(
child: Text('Hello World'),
),
),
);
}
}
非 Material apps
對於非 Material app,你可以將 Center
widget 新增到 app 的 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,
),
),
),
);
}
}
預設情況下,非 Material app 不包含 AppBar
、標題和背景顏色。如果你希望在非 Material app 中使用這些功能,則必須自己建構它們。以上 app 將背景顏色更改為白色,將文字更改為深灰色來模擬一個 Material app。

橫向或縱向佈局多個 widgets
最常見的佈局模式之一是垂直或水平 widgets。你可以使用 Row widget 水平排列 widgets,使用 Column widget 垂直排列 widgets。
要在 Flutter 中建立行或列,可以將子 widgets 列表新增到
Row
或 Column
widget 中。反過來,每個子項本身可以是一行或一列,依此類推。以下範例示範瞭如何在行或列中巢狀(Nesting)行或列。
這個佈局被組織為 Row
。這一行包含兩個子項:左側的列和右側的影象:
左側列的 widget 樹巢狀(Nesting)著行和列。
你將在 巢狀(Nesting)行和列 中實現蛋糕介紹範例的一些佈局程式碼。
對齊 widgets
你可以使用 mainAxisAlignment
和 crossAxisAlignment
屬性控制行或列如何對齊其子項。對於一行來說,主軸水平延伸,交叉軸垂直延伸。對於一列來說,主軸垂直延伸,交叉軸水平延伸。


MainAxisAlignment
和 CrossAxisAlignment
這兩個列舉提供了很多用於控制對齊的常量。
在以下範例中,3 個影象每個都是是 100 畫素寬。渲染框(在本例中是整個螢幕)寬度超過 300 畫素,因此設定主軸對齊方式為 spaceEvenly
會將空餘空間在每個影象之間、之前和之後均勻地劃分。
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Image.asset('images/pic1.jpg'),
Image.asset('images/pic2.jpg'),
Image.asset('images/pic3.jpg'),
],
);
App 原始碼: row_column
列的工作方式與行的工作方式相同。以下範例展示了包含 3 個影象的列,每個影象的高度為 100 畫素。渲染框(在本例中是整個螢幕)高度超過 300 畫素,因此設定主軸對齊方式為 spaceEvenly
會將空餘空間在每個影象之間、之上和之下均勻地劃分。
Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Image.asset('images/pic1.jpg'),
Image.asset('images/pic2.jpg'),
Image.asset('images/pic3.jpg'),
],
);
App 原始碼: row_column

調整 widgets 大小
當某個佈局太大而超出螢幕時,受影響的邊緣會出現黃色和黑色條紋圖案。這裡有一個行太寬的 例子:
透過使用 Expanded
widget,可以調整 widgets 的大小以適合行或列。要修復上一個影象行對其渲染框來說太寬的範例,可以用 Expanded
widget 把每個影象包起來。
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'), ), ], );
App 原始碼: sizing
也許你想要一個 widget 佔用的空間是兄弟項的兩倍。為了達到這個效果,可以使用 Expanded
widget 的 flex
屬性,這是一個用來確定 widget 的彈性係數的整數。預設的彈性係數為 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'),
),
],
);
App 原始碼: sizing
組合 widgets
預設情況下,行或列沿其主軸會佔用盡可能多的空間,但如果要將子項緊密組合在一起,請將其 mainAxisSize
設定為 MainAxisSize.min
。以下範例使用此屬性將星形圖示組合在一起。
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),
],
)
App 原始碼: pavlova
巢狀(Nesting)行和列
佈局框架允許你根據需要在行和列內巢狀(Nesting)行和列。讓我們看看以下佈局的概述部分的程式碼:
概述的部分實現為兩行,評級一行包含五顆星和評論的數量,圖示一行包含由圖示與文字組成的三列。
以下是評級行的 widget 樹形圖:
ratings
變數建立了一個行,其中包含較小的由 5 個星形圖示和文字組成的一行:
var 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,
),
),
],
),
);
評級行下方的圖示行包含 3 列,每列包含一個圖示和兩行文字,你可以在其 widget 樹中看到:
iconList
變數定義了圖示行:
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
變數包含評級和圖示行,以及蛋糕介紹的標題和文字:
final leftColumn = Container(
padding: const EdgeInsets.fromLTRB(20, 30, 20, 20),
child: Column(
children: [
titleText,
subTitle,
ratings,
iconList,
],
),
);
左列放置在 Container
中以限制其寬度。最後,UI 由 Card
內的整行(包含左列和影象)構成。
蛋糕圖片 來自 Pixabay 網站。你可以使用 Image.network()
從網路上參考影象,但是在本例影象將儲存到專案中的一個影象目錄中,新增到 pubspec 檔案,並使用 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
通用佈局 widgets
Flutter 有一個豐富的佈局 widget 儲存庫,裡面有很多經常會用到的佈局 widget。目的是為了讓你更快的上手,而不是被一個完整的列表嚇跑。關於其他有用的 widget 的資訊,可以參考 Widget 目錄,或者使用 API 參考文件 中的搜尋框。而且,API 文件中的 widget 頁面中經常會給出一些關於相似的 widget 哪個會更適合你的建議。
下面的 widget 會分為兩類:widgets 庫 中的標準 widgets 和 Material 庫 中的 widgets。任何 app 都可以使用 widget 庫,但是 Material 庫中的元件只能在 Material app 中使用。
標準 widgets
-
Container
:向 widget 增加 padding、margins、borders、background color 或者其他的“裝飾”。 -
GridView
:將 widget 展示為一個可滾動的網格。 -
ListView
:將 widget 展示為一個可滾動的列表。 -
Stack
:將 widget 覆蓋在另一個的上面。
Material widgets
Container
許多佈局都可以隨意的用 Container
,它可以將使用了 padding 或者增加了 borders/margins 的 widget 分開。你可以透過將整個佈局放到一個 Container
中,並且改變它的背景色或者圖片,來改變裝置的背景。
摘要 (Container)
-
增加 padding、margins、borders
-
改變背景色或者圖片
-
只包含一個子 widget,但是這個子 widget 可以是行、列或者是 widget 樹的根 widget

範例 (Container)
這個佈局包含一個有兩行的列,每行有兩張圖片。
Container
用來將列的背景色變為淺灰色。
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),
],
);
你可以在 佈局建構課程 和 Flutter Gallery
中可以發現更多關於 Container
的例子。
App 原始碼: container
GridView
使用 GridView
將 widget 作為二維列表展示。
GridView
提供兩個預製的列表,或者你可以自訂網格。當 GridView
檢測到內容太長而無法適應渲染盒時,它就會自動支援滾動。
摘要 (GridView)
-
在網格中使用 widget
-
當列的內容超出渲染容器的時候,它會自動支援滾動。
-
建立自訂的網格,或者使用下面提供的網格的其中一個:
-
GridView.count
允許你制定列的數量 -
GridView.extent
允許你制定單元格的最大寬度
-
範例 (GridView)
使用 GridView.count
建立一個網格,它在豎屏模式下有兩行,在橫屏模式下有三行。可以透過為每個 GridTile
設定 footer
屬性來建立標題。
Dart 程式碼: Flutter Gallery 中的 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<Container> _buildGridTileList(int count) => List.generate(
count, (i) => Container(child: 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
在基礎 widget(通常是圖片)上排列 widget,
widget 可以完全或者部分覆蓋基礎 widget。
摘要 (Stack)
-
用於覆蓋另一個 widget
-
子列表中的第一個 widget 是基礎 widget;後面的子項覆蓋在基礎 widget 的頂部
-
Stack
的內容是無法滾動的 -
你可以剪下掉超出渲染框的子項
範例 (Stack)
在 CircleAvatar
的上面使用 Stack
覆蓋 Container
(在透明的黑色背景上展示它的 Text
)。
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
Material 庫 中的 Card
包含相關有價值的資訊,幾乎可以由任何 widget 組成,但是通常和 ListTile
一起使用。
Card
只有一個子項,這個子項可以是列、行、列表、網格或者其他支援多個子項的 widget。預設情況下,Card
的大小是 0x0 畫素。你可以使用 SizedBox
控制 card 的大小。
在 Flutter 中,Card
有輕微的圓角和陰影來使它具有 3D 效果。改變 Card
的 elevation
屬性可以控制陰影效果。例如,把 elevation 設定為 24,可以從視覺上更多的把 Card
抬離表面,使陰影變得更加分散。關於支援的 elevation 的值的列表,可以檢視 Material guidelines 中的 Elevation。使用不支援的值則會使陰影無效。
摘要 (Card)
-
實現一個 Material card
-
用於呈現相關有價值的資訊
-
接收單個子項,但是子項可以是
Row
、Column
或者其他可以包含列表子項的 widget -
顯示圓角和陰影
-
Card
的內容無法滾動 -
來自 Material 庫
範例 (Card)
包含 3 個 ListTile 的 Card
,並且透過被 SizedBox
包住來調整大小。
Divider
分隔了第一個和第二個 ListTiles
。
App 原始碼: card_and_stack
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 庫 中專用的行 widget,它可以很輕鬆的建立一個包含三行文字以及可選的行前和行尾圖示的行。
ListTile
在 Card
或者 ListView
中最常用,但是也可以在別處使用。
摘要 (ListTile)
-
一個可以包含最多 3 行文字和可選的圖示的專用的行
-
比
Row
更少的配置,更容易使用 -
來自 Material 庫
範例 (ListTile)
Constraints
To fully understand Flutter’s layout system, you need to learn how Flutter positions and sizes the components in a layout. For more information, see Understanding constraints.
影片
下面的影片是 Flutter in Focus 系列的一部分,解釋了 Stateless 和 Stateful 的 widget。
每週 Widget 系列 的每一集都會介紹一個 widget。其中也包括一些佈局的 widget。
Flutter Widget of the Week playlist
其他資源
當寫佈局程式碼時,下面的資源可能會幫助到你。
-
Layout 課程)
學習如何建構佈局。 -
核心 Widget 目錄
描述了 Flutter 中很多可用的 widget。 -
給 Web 開發者的 Flutter 指南
對那些熟悉 web 開發的人來說,這頁將 HTML/CSS 的功能對映到 Flutter 特性上。 -
Flutter Gallery 應用和 程式碼儲存庫
Demo app 展示了很多 Material Design widget 和其他的 Flutter 特性。 -
Flutter API 文件
所有 Flutter 庫的參考文件。 -
處理邊界約束 (Box constraints) 的問題
討論 widget 是如何受渲染框限制的。 -
在 Flutter 中新增資源和圖片
解釋在你的 app 中如何新增圖片和其他資源。 -
Flutter 從 0 到 1
一位開發者第一次寫 Flutter app 的經驗分享文章。