Flutter 常用布局元件
Flutter 控件本身通常由許多小型、單用途的控件組成,結合起來産生強大的效果,例如:Container 是一種常用的控件,由負責布局、繪畫、定位和大小調整的幾個控件組成
具體來說:Container 是由 LimitedBox、ConstrainedBox、 Align、Padding、DecoratedBox 和 Transform 控件組成,而不是将 Container 子類化來産生自定義效果,您可以用這種新穎的方式組合這些以及其他簡單的控件
類的層次結構是扁平的,以最大化可能的組合數量
在寫應用程式時,經常會使用 StatelessWidget 和 StatefulWidget 編寫新控件,兩者的差别在于你是否要管理控件的狀态;一個控件的主要任務是實作 build 函數,定義控件中其他較低層次的控件;build 函數将依次建構這些控件,直到底層渲染對象
基本元件
Container
容器,一個常用的控件,由基本的繪制、位置和大小控件組成;負責建立矩形的可視元素,可以用 BoxDecoration 來設計樣式,比如背景、邊框和陰影,Container 也有邊距、填充和大小限制,另外,還可以在三維空間利用矩陣進行變換
沒有子控件的容器盡可能大,除非傳入的大小限制是無限的,在這種情況下,它們盡可能小。有子控件的容器将自己的尺寸給他們的孩子。我們可以通過 width、height 和 constraints 屬性控制 size
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n12" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> 1 new Container(
2 constraints: new BoxConstraints.expand(
3 height: Theme.of(context).textTheme.display1.fontSize * 1.1 + 200.0,
4 ),
5 padding: const EdgeInsets.all(8.0),
6 color: Colors.teal.shade700,
7 alignment: Alignment.center,
8 child: new Text('Hello World', style: Theme.of(context).textTheme.display1.copyWith(color: Colors.white)),
9 foregroundDecoration: new BoxDecoration(
10 image: new DecorationImage(
11 image: new NetworkImage('https://www.example.com/images/frame.png'),
12 centerSlice: new Rect.fromLTRB(270.0, 180.0, 1360.0, 730.0),
13 ),
14 ),
15 transform: new Matrix4.rotationZ(0.1),
16 )</pre>
Row
flex 水準布局控件,能夠将子控件水準排列,是基于 Web 的 flexbox 的布局模式設計的
Row 子控件有靈活與不靈活的兩種,Row 首先列出不靈活的子控件,減去它們的總寬度,計算還有多少可用的空間。然後Row 按照 Flexible.flex 屬性确定的比例在可用空間中列出靈活的子控件。要控制靈活子控件,需要使用 Expanded 控件
注意該控件不支援滑動,如果子控件超過剩餘空間,會報錯,如果想支援水準滑動,考慮使用 ListView
如果隻有一個子控件,可以使用 Align or Center控件定義該子控件位置
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n22" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> 1 new Row(
2 children: <Widget>[
3 new Expanded(
4 child: new Text('Deliver features faster', textAlign: TextAlign.center),
5 ),
6 new Expanded(
7 child: new Text('Craft beautiful UIs', textAlign: TextAlign.center),
8 ),
9 new Expanded(
10 child: new FittedBox(
11 fit: BoxFit.contain, // otherwise the logo will be tiny
12 child: const FlutterLogo(),
13 ),
14 ),
15 ],
16 )</pre>
Column
flex 垂直布局控件,能夠将子控件垂直排列
用法與 Row 控件一樣
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n29" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> 1 new Column(
2 crossAxisAlignment: CrossAxisAlignment.start,
3 mainAxisSize: MainAxisSize.min,
4 children: <Widget>[
5 new Text('We move under cover and we move as one'),
6 new Text('Through the night, we have one shot to live another day'),
7 new Text('We cannot let a stray gunshot give us away'),
8 new Text('We will fight up close, seize the moment and stay in it'),
9 new Text('It’s either that or meet the business end of a bayonet'),
10 new Text('The code word is ‘Rochambeau,’ dig me?'),
11 new Text('Rochambeau!', style: DefaultTextStyle.of(context).style.apply(fontSizeFactor: 2.0)),
12 ],
13 )</pre>
Image
顯示圖像的控件,Image 控件有多種構造函數:
- new Image,用于從 ImageProvider 擷取圖像
- new Image.asset,用于使用 key 從 AssetBundle 擷取圖像
- new Image.network,用于從 URL 位址擷取圖像
- new Image.file,用于從 File 擷取圖像
為了自動執行像素密度感覺資源分辨率,使用 AssetImage 指定圖像,需要確定在控件樹中的圖檔控件上方存在 MaterialApp、WidgetsApp 和 MediaQuery 控件
不同的手機有不同的像素比率,這時就需要根據手機的像素比率來加載不同圖檔,做法很簡單,隻需要在圖檔同級目錄下建立 2.0x/… 和 3.0x/… 的目錄就可以了
我們在 pubspec.yaml 這個檔案裡指定本地圖檔路徑
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n46" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"># To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg</pre>
Text
用來顯示文本的控件
下面的執行個體有7個不同樣式的文本控件:
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n52" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> 1 import 'package:flutter/material.dart';
2 class TextDemo extends StatelessWidget {
3 @override
4 Widget build(BuildContext context) {
5 return new Scaffold(
6 appBar: new AppBar(
7 title: new Text('文本控件'),
8 ),
9 body: new Column(
10 children: <Widget>[
11 new Text(
12 '紅色+黑色删除線+25号',
13 style: new TextStyle(
14 color: const Color(0xffff0000),
15 decoration: TextDecoration.lineThrough,
16 decorationColor: const Color(0xff000000),
17 fontSize: 25.0,
18 ),
19 ),
20 new Text(
21 '橙色+下劃線+24号',
22 style: new TextStyle(
23 color: const Color(0xffff9900),
24 decoration: TextDecoration.underline,
25 fontSize: 24.0,
26 ),
27 ),
28 new Text(
29 '虛線上劃線+23号+傾斜',
30 style: new TextStyle(
31 decoration: TextDecoration.overline,
32 decorationStyle: TextDecorationStyle.dashed,
33 fontSize: 23.0,
34 fontStyle: FontStyle.italic,
35 ),
36 ),
37 new Text(
38 'serif字型+24号',
39 style: new TextStyle(
40 fontFamily: 'serif',
41 fontSize: 26.0,
42 ),
43 ),
44 new Text(
45 'monospace字型+24号+加粗',
46 style: new TextStyle(
47 fontFamily: 'monospace',
48 fontSize: 24.0,
49 fontWeight: FontWeight.bold,
50 ),
51 ),
52 new Text(
53 '天藍色+25号+2行跨度',
54 style: new TextStyle(
55 color: const Color(0xff4a86e8),
56 fontSize: 25.0,
57 height: 2.0,
58 ),
59 ),
60 new Text(
61 '24号+2個字母間隔',
62 style: new TextStyle(
63 fontSize: 24.0,
64 letterSpacing: 2.0,
65 ),
66 ),
67 ]
68 ),
69 );
70 }
71 }
72 void main() {
73 runApp(
74 new MaterialApp(
75 title: 'Flutter教程',
76 home: new TextDemo(),
77 ),
78 );
79 }</pre>
Icon
- 圖示控件,按照IconData中所描述的規則繪制,如Material中預定義的IconDatas
- 該控件不可互動,要實作可互動的圖示,可以考慮使用Material中的 IconButton
- 該控件必須在 Directionality控件裡使用,通常這是由 WidgetsApp 或 MaterialApp 自動引入的
RaisedButton
Material Design 風格的浮動按鈕,以方形紙片樣式懸停在界面上,點選後會産生墨水擴散效果。
避免在 dialog 和 card 控件裡使用,一般彈出式的控件建議使用扁平化按鈕,減少布局層次疊加
使用時,要實作 onPressed 回調方法,否則按鈕處于禁用狀态,預設顯示 disabledColor 樣式的扁平化按鈕,并且此時更改按鈕的顔色不會生效
- 注意該控件的父控件必須是 Material 控件
- 如果你隻需要點選後産生墨水擴散效果,但不想使用按鈕,請考慮直接使用 InkWell 控件
- 如有必要,該按鈕将拉伸以适應子控件大小
Scaffold
Scaffold 實作了基本的Material Design布局結構;也就是說, MaterialApp 的 child 是 Scaffold Widget
在 Material 設計中定義的單個界面上的各種布局元素,在 Scaffold 中都有支援,比如 左邊欄(Drawers)、snack bars、以及 bottom sheets
Scaffold 有下面幾個主要屬性:
- appBar:顯示在界面頂部的一個 AppBar,也就是 Android 中的 ActionBar 、Toolbar
- body:目前界面所顯示的主要内容 Widget
- floatingActionButton:Material設計中所定義的 FAB,界面的主要功能按鈕
- persistentFooterButtons:固定在下方顯示的按鈕,比如對話框下方的确定、取消按鈕
- drawer:側邊欄控件
- backgroundColor: 内容的背景顔色,預設使用的是 ThemeData.scaffoldBackgroundColor 的值
- bottomNavigationBar: 顯示在頁面底部的導航欄
- resizeToAvoidBottomPadding:類似于 Android 中的 android:windowSoftInputMode=”adjustResize”,控制界面内容 body 是否重新布局來避免底部被覆寫了,比如當鍵盤顯示的時候,重新布局避免被鍵盤蓋住内容。預設值為 true
顯示 snackbar 或者 bottom sheet 的時候,需要使用目前的 BuildContext 參數調用 Scaffold.of 函數來擷取 ScaffoldState 對象,然後使用 ScaffoldState.showSnackBar 和 ScaffoldState.showBottomSheet 函數來顯示
要特别注意 Scaffold.of 的參數 BuildContext, 如果包含該 BuildContext 的 Widget 是 Scaffold 的父 Widget,則 Scaffold.of 是無法查找到對應的 ScaffoldState 對象的,Scaffold.of 傳回的是父對象中最近的 Scaffold 中的 ScaffoldState 對象
比如,如果在 Scaffold 的 build 函數中,使用 build 的 BuildContext 參數是可以的:
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n95" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> 1 @override
2 Widget build(BuildContext context) {
3 return new RaisedButton(
4 child: new Text('SHOW A SNACKBAR'),
5 onPressed: () {
6 Scaffold.of(context).showSnackBar(new SnackBar(
7 content: new Text('Hello!'),
8 ));
9 },
10 );
11 }</pre>
如果 build 函數傳回一個 Scaffold 對象,則由于 Scaffold 對象是這個 Widget 的子對象,是以使用這個 build 的 BuildContext 參數是不能查找到 ScaffoldState 對象的
這個時候,通過在 Scaffold 中使用一個 Builder 來提供一個新的 BuildConext :
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n101" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> 1 @override
2 Widget build(BuildContext context) {
3 return new Scaffold(
4 appBar: new AppBar(
5 title: new Text('Demo')
6 ),
7 body: new Builder(
8 // Create an inner BuildContext so that the onPressed methods
9 // can refer to the Scaffold with Scaffold.of().
10 builder: (BuildContext context) {
11 return new Center(
12 child: new RaisedButton(
13 child: new Text('SHOW A SNACKBAR'),
14 onPressed: () {
15 Scaffold.of(context).showSnackBar(new SnackBar(
16 content: new Text('Hello!'),
17 ));
18 },
19 ),
20 );
21 },
22 ),
23 );
24 }</pre>
另外還可以把 build 函數中的 Widget 分别建立,分别引入新的 BuildContext 來擷取 Scaffold
結語
至此,我們今天關于 Flutter 架構 中的常用布局元件就介紹到這裡了;希望以上的内容能夠對大家有所幫助,關于 Flutter 的相關技術問題,我們還要去好好的學習剖析;是以我把工作中遇到的 Flutter 元件開源庫相關問題,以及對網上大部分的資料的收集和整理,最終整合出了一份 《Flutter 進階開發學習手冊》
有需要這份學習手冊的朋友,可以私信發送“手冊”即可 免費擷取;希望大家通過閱讀這份學習手冊,能夠查漏補缺;早日精通 Flutter
Flutter 程式設計原理
Flutter3.3 項目實戰
文章篇幅有限;手冊内容就不完全展示了,有需要的小夥伴可以私信發送“手冊”,即可 免費擷取
資料整理不易,如果覺得内容對你有所幫助的話,可以點贊轉發分享一下哦~
最後祝各位開發者早日精通 Flutter ,攀登上更高的高峰