Flutter控件本身通常由許多小型、單用途的控件組成,結合起來產生強大的效果,例如,Container是一種常用的控件,由負責布局、繪畫、定位和大小調整的幾個控件組成,具體來說,Container是由LimitedBox、ConstrainedBox、 Align、Padding、DecoratedBox和Transform控件組成,而不是將Container子類化來產生自定義效果,您可以用這種新穎的方式組合這些以及其他簡單的控件。
類的層次結構是扁平的,以最大化可能的組合數量。
在寫應用程序時,經常會使用StatelessWidget和StatefulWidget編寫新控件,兩者的差別在於你是否要管理控件的狀態。一個控件的主要任務是實現build函數,定義控件中其他較低層次的控件。build函數將依次構建這些控件,直到底層渲染對象。
基本組件
Container
容器,一個常用的控件,由基本的繪制、位置和大小控件組成。負責創建矩形的可視元素,可以用BoxDecoration來設計樣式,比如背景、邊框和陰影,Container也有邊距、填充和大小限制,另外,還可以在三維空間利用矩陣進行變換。
沒有子控件的容器盡可能大,除非傳入的大小約束是無限的,在這種情況下,它們盡可能小。有子控件的容器將自己的尺寸給他們的孩子。我們可以通過width、height和 constraints屬性控制size。
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 )
Row
flex水平布局控件,能夠將子控件水平排列,是基於Web的flexbox的布局模式設計的。
Row子控件有靈活與不靈活的兩種,Row首先列出不靈活的子控件,減去它們的總寬度,計算還有多少可用的空間。然后Row按照Flexible.flex屬性確定的比例在可用空間中列出靈活的子控件。要控制靈活子控件,需要使用Expanded控件。
注意該控件不支持滑動,如果子控件超過剩余空間,會報錯,如果想支持水平滑動,考慮使用ListView。
如果只有一個子控件,可以使用 Align or Center控件定義該子控件位置。
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 )
Column
flex垂直布局控件,能夠將子控件垂直排列。
用法與Row控件一樣。
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 )
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這個文件里指定本地圖片路徑
# To add assets to your application, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg
Text
用來顯示文本的控件
下面的實例有7個不同樣式的文本控件:
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 }
Icon
圖標控件,按照IconData中所描述的規則繪制,如Material中預定義的IconDatas。
該控件不可交互,要實現可交互的圖標,可以考慮使用Material中的 IconButton。
該控件必須在 Directionality控件里使用,通常這是由WidgetsApp或 MaterialApp自動引入的。
詳見:https://docs.flutter.io/flutter/widgets/Icon-class.html
RaisedButton
Material Design 風格的浮動按鈕,以方形紙片樣式懸停在界面上,點擊后會產生墨水擴散效果。
避免在dialog和card控件里使用,一般彈出式的控件建議使用扁平化按鈕,減少布局層次疊加。
使用時,要實現onPressed回調方法,否則按鈕處於禁用狀態,默認顯示disabledColor樣式的扁平化按鈕,並且此時更改按鈕的顏色不會生效。
注意該控件的父控件必須是Material控件。
如果你只需要點擊后產生墨水擴散效果,但不想使用按鈕,請考慮直接使用InkWell控件。
如有必要,該按鈕將拉伸以適應子控件大小。
詳見:https://docs.flutter.io/flutter/material/RaisedButton-class.html
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 參數是可以的:
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 }
如果 build 函數返回一個 Scaffold 對象,則由於 Scaffold 對象是這個 Widget 的子對象,所以使用這個 build 的 BuildContext 參數是不能查找到 ScaffoldState 對象的,這個時候,通過在 Scaffold 中使用一個 Builder 來提供一個新的 BuildConext :
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 }
另外還可以把 build 函數中的 Widget 分別創建,分別引入新的 BuildContext 來獲取 Scaffold。
Appbar
AppBar 和 SliverAppBar 是Material Design中的 App Bar,也就是 Android 中的 Toolbar,關於 Toolbar 的設計指南請參考Material Design中 Toolbar 的內容。
AppBar 和 SliverAppBar 都是繼承StatefulWidget 類,都代表 Toobar,二者的區別在於 AppBar 位置的固定的應用最上面的;而 SliverAppBar 是可以跟隨內容滾動的。
他們的主要屬性如下:
-
leading:在標題前面顯示的一個控件,在首頁通常顯示應用的 logo;在其他界面通常顯示為返回按鈕
-
title: Toolbar 中主要內容,通常顯示為當前界面的標題文字
-
actions:一個 Widget 列表,代表 Toolbar 中所顯示的菜單,對於常用的菜單,通常使用 IconButton 來表示;對於不常用的菜單通常使用 PopupMenuButton 來顯示為三個點,點擊后彈出二級菜單
-
bottom:一個 AppBarBottomWidget 對象,通常是 TabBar。用來在 Toolbar 標題下面顯示一個 Tab 導航欄
-
elevation:紙墨設計中控件的 z 坐標順序,默認值為 4,對於可滾動的 SliverAppBar,當 SliverAppBar 和內容同級的時候,該值為 0, 當內容滾動 SliverAppBar 變為 Toolbar 的時候,修改 elevation 的值
flexibleSpace:一個顯示在 AppBar 下方的控件,高度和 AppBar 高度一樣,可以實現一些特殊的效果,該屬性通常在 SliverAppBar 中使用 -
backgroundColor:APP bar 的顏色,默認值為 ThemeData.primaryColor。改值通常和下面的三個屬性一起使用
-
brightness:App bar 的亮度,有白色和黑色兩種主題,默認值為 ThemeData.primaryColorBrightness
-
iconTheme:App bar 上圖標的顏色、透明度、和尺寸信息。默認值為 ThemeData.primaryIconTheme
-
textTheme: App bar 上的文字樣式。默認值為 ThemeData.primaryTextTheme
-
centerTitle: 標題是否居中顯示,默認值根據不同的操作系統,顯示方式不一樣
1 import 'package:flutter/material.dart'; 2 3 class AppBarBottomSample extends StatefulWidget { 4 @override 5 _AppBarBottomSampleState createState() => new _AppBarBottomSampleState(); 6 } 7 8 class _AppBarBottomSampleState extends State<AppBarBottomSample> with SingleTickerProviderStateMixin { 9 TabController _tabController; 10 11 @override 12 void initState() { 13 super.initState(); 14 _tabController = new TabController(vsync: this, length: choices.length); 15 } 16 17 @override 18 void dispose() { 19 _tabController.dispose(); 20 super.dispose(); 21 } 22 23 void _nextPage(int delta) { 24 final int newIndex = _tabController.index + delta; 25 if (newIndex < 0 || newIndex >= _tabController.length) 26 return; 27 _tabController.animateTo(newIndex); 28 } 29 30 @override 31 Widget build(BuildContext context) { 32 return new MaterialApp( 33 home: new Scaffold( 34 appBar: new AppBar( 35 title: const Text('AppBar Bottom Widget'), 36 leading: new IconButton( 37 tooltip: 'Previous choice', 38 icon: const Icon(Icons.arrow_back), 39 onPressed: () { _nextPage(-1); }, 40 ), 41 actions: <Widget>[ 42 new IconButton( 43 icon: const Icon(Icons.arrow_forward), 44 tooltip: 'Next choice', 45 onPressed: () { _nextPage(1); }, 46 ), 47 ], 48 bottom: new PreferredSize( 49 preferredSize: const Size.fromHeight(48.0), 50 child: new Theme( 51 data: Theme.of(context).copyWith(accentColor: Colors.white), 52 child: new Container( 53 height: 48.0, 54 alignment: Alignment.center, 55 child: new TabPageSelector(controller: _tabController), 56 ), 57 ), 58 ), 59 ), 60 body: new TabBarView( 61 controller: _tabController, 62 children: choices.map((Choice choice) { 63 return new Padding( 64 padding: const EdgeInsets.all(16.0), 65 child: new ChoiceCard(choice: choice), 66 ); 67 }).toList(), 68 ), 69 ), 70 ); 71 } 72 } 73 74 class Choice { 75 const Choice({ this.title, this.icon }); 76 final String title; 77 final IconData icon; 78 } 79 80 const List<Choice> choices = const <Choice>[ 81 const Choice(title: 'CAR', icon: Icons.directions_car), 82 const Choice(title: 'BICYCLE', icon: Icons.directions_bike), 83 const Choice(title: 'BOAT', icon: Icons.directions_boat), 84 const Choice(title: 'BUS', icon: Icons.directions_bus), 85 const Choice(title: 'TRAIN', icon: Icons.directions_railway), 86 const Choice(title: 'WALK', icon: Icons.directions_walk), 87 ]; 88 89 class ChoiceCard extends StatelessWidget { 90 const ChoiceCard({ Key key, this.choice }) : super(key: key); 91 92 final Choice choice; 93 94 @override 95 Widget build(BuildContext context) { 96 final TextStyle textStyle = Theme.of(context).textTheme.display1; 97 return new Card( 98 color: Colors.white, 99 child: new Center( 100 child: new Column( 101 mainAxisSize: MainAxisSize.min, 102 crossAxisAlignment: CrossAxisAlignment.center, 103 children: <Widget>[ 104 new Icon(choice.icon, size: 128.0, color: textStyle.color), 105 new Text(choice.title, style: textStyle), 106 ], 107 ), 108 ), 109 ); 110 } 111 } 112 113 void main() { 114 runApp(new AppBarBottomSample()); 115 }
FlutterLogo
定義flutter應用的logo,該控件受IconTheme約束。
1 import 'package:flutter/material.dart'; 2 3 void main() { 4 runApp(new FadeAppTest()); 5 } 6 7 class FadeAppTest extends StatelessWidget { 8 // This widget is the root of your application. 9 @override 10 Widget build(BuildContext context) { 11 return new MaterialApp( 12 title: 'Fade Demo', 13 theme: new ThemeData( 14 primarySwatch: Colors.blue, 15 ), 16 home: new MyFadeTest(title: 'Fade Demo'), 17 ); 18 } 19 } 20 21 class MyFadeTest extends StatefulWidget { 22 MyFadeTest({Key key, this.title}) : super(key: key); 23 final String title; 24 @override 25 _MyFadeTest createState() => new _MyFadeTest(); 26 } 27 28 class _MyFadeTest extends State<MyFadeTest> with TickerProviderStateMixin { 29 AnimationController controller; 30 CurvedAnimation curve; 31 32 @override 33 void initState() { 34 controller = new AnimationController(duration: const Duration(milliseconds: 2000), vsync: this); 35 curve = new CurvedAnimation(parent: controller, curve: Curves.easeIn); 36 } 37 38 @override 39 Widget build(BuildContext context) { 40 return new Scaffold( 41 appBar: new AppBar( 42 title: new Text(widget.title), 43 ), 44 body: new Center( 45 child: new Container( 46 child: new FadeTransition( 47 opacity: curve, 48 child: new FlutterLogo( 49 size: 100.0, 50 )))), 51 floatingActionButton: new FloatingActionButton( 52 tooltip: 'Fade', 53 child: new Icon(Icons.brush), 54 onPressed: () { 55 controller.forward(); 56 }, 57 ), 58 ); 59 } 60 }
Placeholder
占位控件,該控件繪制一個框,表示將來會在該位置添加其他控件。
這個控件在開發過程中很有用,可提示該處接口還沒完成。
默認情況下,控件的大小自適應其容器。如果該控件處於無界空間,它將根據給定的fallbackWidth和fallbackHeight自行調整大小。
詳見:https://docs.flutter.io/flutter/widgets/Placeholder-class.html
Flutter和原生Android控件對比
Flutter控件 | Android控件 |
---|---|
AppBar | ActionBar/ToolBar |
ListView | ListView/RecyclerView |
Text | TextView |
Center | ViewGroup |
Container | RelativeLayout |
FloatingActionButton | FloatingActionButton(design庫里面的) |
BottomNavigationBar | BottomNavigation(design庫里面的) |
RaisedButton/Button | Button |
Column | LinearLayout的android:orientation="vertical" |
Row | android:orientation="horizontal" |
DecorationImage | ImageView |
Image | ImageView |
Stack | FrameLayout/RelativeLayout |
Algin | alginParentXXX屬性 |
resizeToAvoidBottomPadding | android:windowSoftInputMode=”adjustResize屬性 |
SingleChildScrollView | ScrollView |
CustomScrollerView | Recyclerview |
Image里面的BoxFit參數介紹:(相當於Android的ImageView的scaleType參數)
// fill 通過篡改原始寬高比來填充目標box
/// contain 在盡可能大的情況下,仍然將源完全包含在目標框中。
/// cover 盡可能小,同時仍然覆蓋整個目標框。
/// fitWidth 確保顯示源的全部寬度,而不管這是否意味着源垂直溢出目標框。
/// fitHeight 確保顯示源的全部高度,而不管這是否意味着源水平地溢出目標框。
/// none 在目標框中對齊源(默認為居中),並放棄位於框外的源的任何部分。源圖像未調整大小。
/// scaleDown 在目標框中對齊源(默認為居中),如果需要,將源縮小以確保源適合該框。這與contain的內容相同,如果該內容會收縮圖像,那么它就是none。