Flutter常用布局組件


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 }

 

 

這里寫圖片描述

定義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。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM