Flutter 中的佈局
Flutter 佈局機制的核心是 Widget。在 Flutter 中,幾乎所有東西都是 Widget,甚至連佈局模型也是 Widget。您在 Flutter 應用程式中看到的圖像、圖示和文字都是 Widget。但您看不到的東西也是 Widget,例如排列、約束和對齊可見 Widget 的列、欄和網格。
您藉由組合 Widget 來建立佈局,以建構更複雜的 Widget。例如,下面的第一個螢幕截圖顯示了 3 個圖示,每個圖示下方都有一個標籤。
第二個螢幕截圖顯示視覺佈局,顯示一列 3 個欄,每個欄都包含一個圖示和一個標籤。
以下是此 UI 的 Widget 樹狀圖
大部分內容應該如您所預期的那樣,但您可能會對容器(以粉紅色顯示)感到好奇。Container
是一個 Widget 類別,可讓您自訂其子 Widget。當您想要新增邊距、外邊距、邊框或背景顏色時,請使用 Container
,這只是它部分的功能。
在此範例中,每個Text
Widget 都放置在 Container
中以新增邊距。整個Row
也放置在 Container
中,以便在列周圍新增內邊距。
此範例中的其餘 UI 由屬性控制。使用Icon
的 color
屬性來設定其顏色。使用 Text.style
屬性來設定字型、其顏色、粗細等等。欄和列具有屬性,可讓您指定其子項目如何垂直或水平對齊,以及子項目應佔用多少空間。
配置小工具
#您如何在 Flutter 中佈局單一 Widget?本節說明如何建立和顯示簡單的 Widget。它還顯示一個簡單 Hello World 應用程式的完整程式碼。
在 Flutter 中,只需幾個步驟即可在螢幕上放置文字、圖示或圖片。
1. 選取版面配置小工具
#根據您想要對齊或約束可見 Widget 的方式,從各種佈局 Widget中選擇,因為這些特性通常會傳遞給包含的 Widget。
此範例使用 Center
,它會在水平和垂直方向將內容置中。
2. 建立可見的小工具
#例如,建立一個Text
Widget
Text('Hello World'),
建立一個Image
Widget
return Image.asset(
image,
fit: BoxFit.cover,
);
建立一個Icon
Widget
Icon(
Icons.star,
color: Colors.red[500],
),
3. 將可見的小工具新增至版面配置小工具
#所有佈局 Widget 都具有以下其中一個
- 如果它們採用單一子項目,則使用
child
屬性,例如Center
或Container
- 如果它們採用 Widget 清單,則使用
children
屬性,例如Row
、Column
、ListView
或Stack
。
將 Text
Widget 新增至 Center
Widget
const Center(
child: Text('Hello World'),
),
4. 將版面配置小工具新增至頁面
#Flutter 應用程式本身就是一個 Widget,大多數 Widget 都有一個 build()
方法。在應用程式的 build()
方法中實例化並傳回 Widget 會顯示該 Widget。
Material 應用程式
#對於 Material
應用程式,您可以使用Scaffold
Widget;它提供預設的橫幅、背景顏色,並具有新增抽屜、快顯訊息和底部工作表的 API。然後,您可以將 Center
Widget 直接新增至首頁的 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 應用程式
#若要建立 Cupertino
應用程式,請使用 CupertinoApp
和CupertinoPageScaffold
Widget。
與 Material
不同,它不提供預設的橫幅或背景顏色。您需要自行設定這些。
若要設定預設顏色,請將設定好的
CupertinoThemeData
傳遞至應用程式的theme
屬性。若要將 iOS 風格的導覽列新增至應用程式的頂部,請將
CupertinoNavigationBar
Widget 新增至 Scaffold 的navigationBar
屬性。您可以使用CupertinoColors
提供的顏色,將 Widget 設定為符合 iOS 設計。若要佈局應用程式的主體,請將 Scaffold 的
child
屬性設定為所需 Widget 的值,例如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: <Widget>[
Text('Hello World'),
],
),
),
),
);
}
}
非 Material 應用程式
#對於非 Material 應用程式,您可以將 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,
),
),
),
);
}
}
依預設,非 Material 應用程式不包含 AppBar
、標題或背景顏色。如果您想要在非 Material 應用程式中使用這些功能,您必須自行建構它們。此應用程式將背景顏色變更為白色,並將文字變更為深灰色,以模擬 Material 應用程式。
垂直和水平配置多個小工具
#最常見的佈局模式之一是垂直或水平排列 Widget。您可以使用 Row
Widget 水平排列 Widget,並使用 Column
Widget 垂直排列 Widget。
若要在 Flutter 中建立列或欄,請將子 Widget 清單新增至Row
或Column
Widget。反過來,每個子項目本身都可以是列或欄,依此類推。以下範例說明如何在列或欄中巢狀嵌入列或欄。
此佈局組織為 Row
。列包含兩個子項目:左側的欄和右側的圖片
左欄的 Widget 樹狀結構會巢狀嵌入列和欄。
您將在巢狀嵌入列和欄中實作 Pavlova 的部分佈局程式碼。
對齊小工具
#您可以使用 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'),
],
);
應用程式原始碼: 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'),
],
);
應用程式原始碼: row_column
調整小工具大小
#當佈局太大而無法放入裝置時,受影響的邊緣會出現黃色和黑色條紋圖案。以下是列太寬的範例
可以使用Expanded
Widget 將 Widget 調整為適合列或欄內。若要修正先前圖片列對於其渲染框來說太寬的範例,請使用 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'),
),
],
);
應用程式原始碼: sizing
也許您想要一個小部件佔用其兄弟部件兩倍的空間。為此,請使用 Expanded
小部件的 flex
屬性,它是一個整數,決定了小部件的彈性因子。預設的彈性因子為 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
包裝小工具
#預設情況下,水平或垂直排列的小部件會盡可能沿其主軸佔用空間,但如果您希望將子部件緊密地排列在一起,請將其 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),
],
)
應用程式原始碼: pavlova
巢狀列和欄
#版面配置框架允許您根據需要將水平和垂直排列的小部件巢狀於其他水平和垂直排列的小部件中。讓我們看看以下版面配置中,帶有外框的部分的程式碼。
帶有外框的部分實作為兩個水平排列的小部件。評分列包含五顆星和評論數。圖示列包含三個圖示和文字的垂直排列小部件。
評分列的小部件樹狀結構
ratings
變數建立一個水平排列的小部件,其中包含一個較小的 5 星圖示水平排列小部件和文字
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,
),
),
],
),
);
評分列下方的圖示列包含 3 個垂直排列的小部件;每個垂直排列的小部件都包含一個圖示和兩行文字,如其小部件樹狀結構所示
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,
],
),
);
左側的垂直排列小部件放置在 SizedBox
中以約束其寬度。最後,UI 是使用整個水平排列的小部件(包含左側的垂直排列小部件和圖片)在 Card
內建構而成。
此帕芙洛娃蛋糕圖片來自 Pixabay。您可以使用 Image.network()
從網路嵌入圖片,但在此範例中,圖片會儲存到專案中的 images 目錄,新增到 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,
],
),
),
),
),
應用程式原始碼: pavlova
常見的版面配置小工具
#Flutter 擁有豐富的版面配置小部件庫。以下是一些最常用的。目的是讓您盡快開始使用,而不是讓您被完整的清單淹沒。如需有關其他可用小部件的資訊,請參閱小部件目錄,或使用 API 參考文件中的搜尋方塊。此外,API 文件中的小部件頁面通常會提出有關可能更適合您需求的類似小部件的建議。
以下小部件分為兩類:來自小部件庫的標準小部件,以及來自Material 庫的特殊小部件。任何應用程式都可以使用小部件庫,但只有 Material 應用程式可以使用 Material 元件庫。
標準小工具
#Container
:為小部件新增內距、外距、邊框、背景顏色或其他裝飾。GridView
:將小部件排列為可捲動的格線。ListView
:將小部件排列為可捲動的清單。Stack
:將一個小部件重疊在另一個小部件之上。
Material 小工具
#Container
#許多版面配置會大量使用Container
,以使用內距分隔小部件,或新增邊框或外距。您可以透過將整個版面配置放入 Container
並變更其背景顏色或圖片,來變更裝置的背景。
摘要 (Container)
- 新增內距、外距、邊框
- 變更背景顏色或圖片
- 包含單一子小部件,但該子小部件可以是水平或垂直排列的小部件,甚至是小部件樹狀結構的根
範例 (Container)
#此版面配置包含一個垂直排列的小部件,其中包含兩列,每列包含 2 個圖片。使用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),
],
);
您可以在教學課程中找到更多 Container
範例。
應用程式原始碼: container
GridView
#使用 GridView
將小部件排列為二維清單。GridView
提供兩個預製清單,或者您可以建立自己的自訂格線。當 GridView
偵測到其內容太長而無法放入轉譯方塊時,它會自動捲動。
摘要 (GridView)
#- 將小部件排列在格線中
- 偵測到垂直排列小部件內容超出轉譯方塊時,會自動提供捲動
- 建立自己的自訂格線,或使用提供的格線之一
GridView.count
允許您指定欄數GridView.extent
允許您指定磚塊的最大像素寬度
範例 (GridView)
#使用 GridView.extent
建立磚塊最大寬度為 150 像素的格線。
應用程式原始碼: grid_and_list
使用 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<Container> _buildGridTileList(int count) => List.generate(
count, (i) => Container(child: Image.asset('images/pic$i.jpg')));
ListView
#ListView
(類似垂直排列小部件的小部件)在其內容對於其轉譯方塊過長時,會自動提供捲動功能。
摘要 (ListView)
#- 專門的
Column
,用於整理方塊清單 - 可以水平或垂直排列
- 偵測到其內容無法容納時,會提供捲動功能
- 比
Column
的可設定性低,但更容易使用並支援捲動
範例 (ListView)
#使用 ListView
顯示使用 ListTile
的商家清單。Divider
將電影院與餐廳分開。
應用程式原始碼: grid_and_list
使用 ListView
顯示來自特定色系的 Colors
(來自 Material 2 設計調色盤)。
Dart 程式碼: colors_demo.dart
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
在基本小部件(通常是圖片)之上排列小部件。這些小部件可以完全或部分重疊基本小部件。
摘要 (Stack)
#- 用於重疊另一個小部件的小部件
- 子項清單中的第一個小部件是基本小部件;後續子項會覆蓋在該基本小部件之上
Stack
的內容無法捲動- 您可以選擇剪裁超出轉譯方塊的子項
範例 (Stack)
#使用 Stack
將 Container
(在半透明黑色背景上顯示其 Text
)覆蓋在 CircleAvatar
之上。Stack
使用 alignment
屬性和 Alignment
來偏移文字。
應用程式原始碼: card_and_stack
使用 Stack
將圖示覆蓋在圖片之上。
Dart 程式碼: bottom_navigation_demo.dart
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
包含相關資訊,且幾乎可以由任何小部件組成,但通常與ListTile
一起使用。Card
有一個子項,但其子項可以是垂直或水平排列的小部件、清單、格線或其他支援多個子項的小部件。預設情況下,Card
會將其大小縮小為 0 x 0 像素。您可以使用 SizedBox
來約束卡片的大小。
在 Flutter 中,Card
具有稍微圓角的邊角和陰影,使其具有 3D 效果。變更 Card
的 elevation
屬性可讓您控制陰影效果。例如,將陰影設定為 24 會使 Card
在視覺上進一步脫離表面,並導致陰影變得更加分散。如需支援的陰影值清單,請參閱Material 指南中的陰影。指定不支援的值會完全停用陰影。
摘要 (Card)
#- 實作Material 卡片
- 用於呈現相關資訊
- 接受單一子項,但該子項可以是
Row
、Column
或其他包含子項清單的小部件 - 以圓角和陰影顯示
Card
的內容無法捲動- 來自Material 庫
範例 (Card)
#包含 3 個 ListTile 且透過使用 SizedBox
包裝來調整大小的 Card
。Divider
分隔第一個和第二個 ListTiles
。
應用程式原始碼: card_and_stack
包含圖片和文字的 Card
。
Dart 程式碼: cards_demo.dart
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('[email protected]'),
leading: Icon(
Icons.contact_mail,
color: Colors.blue[500],
),
),
],
),
),
);
}
列表磚 (ListTile)
#使用ListTile
(來自Material 庫的專用行小部件),以簡易方式建立包含最多 3 行文字和選擇性前置和後置圖示的行。ListTile
最常用於Card
或 ListView
中,但也可以在其他地方使用。
摘要 (ListTile)
#- 包含最多 3 行文字和選擇性圖示的專用行
- 比
Row
的可設定性低,但更容易使用 - 來自Material 庫
範例 (ListTile)
#包含 3 個 ListTile
的 Card
。
應用程式原始碼: card_and_stack
搭配前置小部件使用 ListTile
。
Dart 程式碼: list_demo.dart
約束 (Constraints)
#若要充分了解 Flutter 的版面配置系統,您需要了解 Flutter 如何在版面配置中定位和調整元件大小。如需更多資訊,請參閱了解約束。
影片
#以下影片是Flutter in Focus 系列的一部分,說明 Stateless
和 Stateful
小部件。
Widget of the Week 系列的每一集都專注於一個小部件。其中幾個包含版面配置小部件。
Flutter Widget of the Week 播放清單
其他資源
#下列資源可能對撰寫版面配置程式碼有所幫助。
- 版面配置教學課程
- 了解如何建立版面配置。
- 小工具目錄
- 描述 Flutter 中可用的許多小工具 (widget)。
- Flutter 中與 HTML/CSS 對應之處
- 對於熟悉網頁程式設計的人來說,此頁面將 HTML/CSS 的功能對應到 Flutter 的特性。
- API 參考文件
- 所有 Flutter 函式庫的參考文件。
- 新增資源和圖片
- 說明如何將圖片和其他資源新增至您的應用程式套件。
- 使用 Flutter 從零到一
- 某人撰寫他的第一個 Flutter 應用程式的經驗。
除非另有說明,本網站上的文件反映了 Flutter 的最新穩定版本。頁面最後更新於 2024-11-25。 檢視原始碼 或回報問題。