Flutter 學習筆記-基礎篇
如果你要獲取與該筆記配套的源碼,請點擊這裏。
一、常用命令及快捷鍵
1. 常用命令
flutter doctor //Flutter自檢命令
flutter run //運行項目到設備上
2. 常用快捷鍵
r 鍵:熱加載(快速部署)。
p 鍵:顯示網格,用於開發時調試UI。
o 鍵:切換Android和iOS的預覽模式。
q 鍵:退出調試預覽模式。
以上幾個按鍵需要已經執行過flutter run
之後纔可以在終端中使用。
二、基本Widget組件
Flutter中一切皆組件(Widget)。所有的自定義組件、系統組件、自定義頁面等其實都是Widget的子類。每個widget的構造函數都有一個key
參數,這個參數的作用是什麼呢?Key用於在widget的位置改變時保留其狀態。比如,保留用戶的滑動位置,或者在保留widget狀態的情況下修改一個widget集合,如Row、Column等,這一篇博客詳細d 講解了Widget中的key。
1. 有狀態無狀態組件。
Flutter中如果要自定組件,一般都繼承自有狀態組件或無狀態組件(小到組件大到頁面),如果一個頁面加載出來之後就不會再改變了那麼就繼承自StatelessWidget,如果加載之後還需要根據數據的變化而變化,那麼就需要繼承自StatefulWidget。
1). StatelessWidget 無狀態組件。
StatelessWidget組件是一個抽象類,繼承該組件必須要實現Widget build(BuildContext context)
方法。
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return Text("Hello Flutter!");
}
}
2). StatefulWidget 有狀態組件。
StatefulWidget是一個抽象組件,繼承該組件必須要實現State<StatefulWidget> createState()
方法。該方法需要返回一個State類的實例。而State是一個抽象類,要繼承State需要實現Widget build(BuildContext context)
抽象方法,並制定泛型爲擁有該State的Widget。
如果要實現改變狀態,需要使用State類中的setState方法。下面的栗子是點擊按鈕後按鈕上面的數字動態加1,具體代碼如下:
class TestPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(20),
child: Custom(),
);
}
}
class Custom extends StatefulWidget{
@override
State<StatefulWidget> createState() {
return _CustomState();
}
}
class _CustomState extends State<Custom>{
var myNumber = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
Text("$myNumber"),
SizedBox(height: 20),
RaisedButton(
onPressed: () {
setState(() {
myNumber++;
});
},
child: Text('增加'),
)
],
);
}
}
2. 裝飾組件
1). MaterialApp
MaterialApp是一個方便的Widget,他封裝了應用程序實現 Material Design 所需要的一些Widget。一般作爲一個頁面的最頂層的Widget使用。
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
home | Widget | 是 | -- | 頁面的具體內容組件。 |
title | String | 是 | '' | 頁面標題。 |
color | Color | 是 | -- | 頁面顏色。 |
theme | ThemeData | 是 | -- | 頁面主題。 |
routes | Map<String, WidgetBuilder> | 是 | const <String, WidgetBuilder>{} | 路由。 |
2). Scaffold
Scaffold 是 Material Design 佈局結構的基本實現。此類提供了用於顯示 drawer、snackbar 和底部的 sheet 的參數。
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
appBar | Widget | 是 | -- | 導航欄 |
body | Widget | 是 | -- | |
drawer | Widget | 是 | -- | |
bottomNavigationBar | Widget | 是 | -- |
3). InkWell & GestureDetector 手勢組件
在Flutter中並不是所有的Widget都支持點擊事件,但是如果我們想給某些我們自己的Widget設置點擊事件時應該怎麼辦呢(例如我們列表中的item)?這時我們只需要使用InkWell將我們要設置點擊事件的Widget包裹起來就可以了。
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
child | Widget | 是 | -- | 子組件 |
onTap | GestureTapCallback | 是 | -- | 單擊事件回調 |
onDoubleTap | GestureTapCallback | 是 | -- | 雙擊事件回調 |
onLongPress | GestureLongPressCallback | 是 | -- | 長按事件回調 |
GestureDetector和InkWell用法基本一致,只是GestureDetector比InkWell多了更多回調設置,可以監聽更多的事件。
3. Container 容器組件
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
constraints | BoxConstraints | 是 | -- | 用不約束寬高(最大寬高、最小寬高等) |
alignment | AlignmentGeometry | 是 | -- | 對齊方式 |
decoration | Decoration | 是 | -- | 設置邊框裝飾 |
padding | EdgeInsetsGeometry | 是 | -- | 內邊距 |
margin | EdgeInsetsGeometry | 是 | -- | 外邊距 |
transform | Matrix4 | 是 | -- | 旋轉、平移 |
4. Text 文本組件。
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
data | String | 否 | -- | 用於設置現在界面上的文字 |
textAlign | TextAlign | 是 | -- | center:居中 / left:居左 / right:居右 / justfy:兩端對齊 |
textDirection | TextDirection | 是 | -- | 文本方向,ltr:從左至右 rtl:從右至左 |
overflow | TextOverflow | 是 | -- | 文字超出屏幕後的處理方式,clip:裁剪 fade:漸隱 ellipsis:省略號 |
style | TextStyle | 是 | -- | 用於設置文字的樣式 |
maxLines | int | 是 | -- | 用於設置文字的最大顯示行數 |
textScaleFactor | double | 是 | -- | 字體顯示倍率 |
5. ListView 列表組件。
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
itemCount | Int | 必填 | -- | 設置列表一共有多少個條目 |
itemBuilder | Widget Function(BuildContext context, int index) |
必填 | -- | 用於構建item(cell)視圖的回調方法。 |
scrollDirection | Axis | 是 | Axis.vertical | 滾動方向:horizontal-橫向 vertical縱向。 |
reverse | bool | 是 | false | 是否反向排列。 |
keyboardDismissBehavior | ScrollViewKeyboardDismissBehavior | 是 | ScrollViewKeyboardDismissBehavior.manual | 滾動時鍵盤的處理方式。 |
靜態列表
@override
Widget build(BuildContext context) {
return ListView(
children: [
Text('我是一個條目'),
Text('我是一個條目'),
Text('我是一個條目'),
Text('我是一個條目'),
Text('我是一個條目'),
Text('我是一個條目'),
],
);
}
動態列表
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: 5,
itemBuilder: (context, position){
return ListTile(
leading: item.avatarUrl,
title: item.name,
subtitle: item.desc,
);
}
);
}
6. GridView
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
crossAxisSpacing | double | 是 | -- | 水平間距 |
mainAxisSpacing | double | 是 | -- | 垂直間距 |
scrollDirection | Axis | 是 | Axis.vertical | 滾動方向:horizontal-橫向 vertical縱向。 |
reverse | bool | 是 | false | 是否反向排列。 |
childAspecet | double | 是 | -- | item的寬高比例。 |
crossAxisCount | int | 是 | -- | 設置列數 |
gridDelegate | SliverGridDelegate | 必填 | -- | builder專用。用於設置間距列數等。 |
itemBuilder | Widget Function(BuildContext context, int index) |
必填 | -- | builder專用。用於構建item(cell)視圖的回調方法。 |
靜態列表
@override
Widget build(BuildContext context) {
return GridView.count(
crossAxisCount: 2,
children: [
Text('我是一個條目'),
Text('我是一個條目'),
Text('我是一個條目'),
Text('我是一個條目'),
Text('我是一個條目'),
Text('我是一個條目'),
Text('我是一個條目'),
Text('我是一個條目'),
Text('我是一個條目'),
Text('我是一個條目')
],
);
}
動態列表
@override
Widget build(BuildContext context) {
return GridView.builder(
itemCount: 600,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3
),
itemBuilder: (context, position){
return Text('我是第$position個條目');
}
);
}
7. Padding 內邊距組件
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
padding | EdgeInsetsGeometry | 必填 | -- | 用於設置內邊距,其實是設置child的外邊距。 |
child | Widget | 是 | -- | 子元素。 |
8. Row 水平佈局組件
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
mainAxisAlignment | MainAxisAlignment | 是 | MainAxisAlignment.start | 主軸的排序方式 |
crossAxisAlignment | CrossAxisAlignment | 是 | CrossAxisAlignment.center | 次軸的排序方式 |
children | List<Widget> | 是 | const <Widget>[] | 子組件 |
9. Column 垂直佈局組件
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
mainAxisAlignment | MainAxisAlignment | 是 | MainAxisAlignment.start | 主軸的排序方式 |
crossAxisAlignment | CrossAxisAlignment | 是 | CrossAxisAlignment.center | 次軸的排序方式 |
children | List<Widget> | 是 | const <Widget>[] | 子組件 |
10. Expanded 組件,類似Web中的Flex佈局。
Expanded組件作爲Column或Row組件的子組件使用。可設置當前組件佔用父組件的比例。
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
flex | Int | 是 | 1 | 佔父組件的比例(權重) |
child | Widget | 必填 | -- | 子組件 |
11. SizedBox 佔位組件
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
width | double | 是 | -- | 寬度 |
height | double | 是 | -- | 高度 |
child | Widget | 必填 | -- | 子組件 |
12. Stack 層疊組件
Stack組件可以單獨使用,也可配合 Align 和 Positioned 實現定位佈局。其實Stack有點類似於Android原生的幀佈局。
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
alignment | AlignmentGeometry | 是 | AlignmentDirectional.topStart | 所有子組件的顯示位置 |
children | List<Widget> | 是 | const <Widget>[] | 子組件 |
overflow | Overflow | 是 | Overflow.clip | 子組件溢出後的處理方式 |
Stack(
alignment: Alignment.center,
children: [
Align(
alignment: Alignment.topCenter,
child: Container(
width: 100,
height: 100,
color: Colors.blue,
),
),
Container(
width: 100,
height: 100,
color: Colors.orange,
),
Positioned(
bottom: 10,
child: Container(
width: 100,
height: 100,
color: Colors.green,
),
)
],
);
13. Align
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
alignment | AlignmentGeometry | 是 | Alignment.center | 所有子組件的顯示位置 |
child | Widget | 是 | -- | 子組件 |
14. Positioned
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
top | double | 是 | -- | 距頂部的距離 |
bottom | double | 是 | -- | 距底部的距離 |
left | double | 是 | -- | 距左邊的距離 |
right | double | 是 | -- | 距右邊的距離 |
child | Widget | 是 | -- | 子組件 |
width | double | 是 | -- | 寬度 |
height | double | 是 | -- | 高度 |
15. AspectRatio
作用是可以設置子組件的寬高比。
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
aspectRatio | double | 是 | -- | 設置寬高比例 |
child | Widget | 是 | -- | 子組件 |
16. Card
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
margin | EdgeInsetsGeometry | 是 | -- | 外邊距 |
shape | ShapeBorder | 是 | -- | 邊框及陰影效果,還可以使用RoundedRectangleBorder對其設置圓角 |
clipBehavior | Clip | 是 | Clip.none | 對子組件的裁剪方式 |
elevation | doublle | 是 | 1.0 | 陰影高度 |
child | Widget | 是 | -- | 子組件 |
17. CircleAvatar 原型頭像組件
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
backgroundImage | ImageProvider | 是 | -- | 設置頭像 |
onBackgroundImageError | Function(dynamic exception, StackTrace stackTrace) | 是 | -- | 圖片加載失敗的回調 |
18. Wrap組件(流式佈局組件)
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
spacing | double | 是 | 0.0 | 主軸的子組件之間的間隔 |
runSpacing | double | 是 | 0.0 | 次軸的子組件之間的間隔 |
direction | Axis | 是 | Axis.horizontal | 子組件的排列方向,horizontal橫向,vertical縱向。 |
alignment | WrapAlignment | 是 | WrapAlignment.start | 主軸子組件的對齊方式。 |
runAlignment | WrapAlignment | 是 | WrapAlignment.start | 次軸子組件的對齊方式。 |
textDirection | TextDirection | 是 | -- | 文本的排列方向。 |
19. RaisedButton & MaterialButton & FlatButton & OutlineButton & IconButton & FloatingActiongButton 按鈕組件。
Flutter中給我們預先定義好了一些按鈕控件給我們用,常用的按鈕如下
-
RaisedButton :凸起的按鈕,其實就是Android中的Material Design風格的Button ,繼承自MaterialButton
如果要讓RaisedButton支持圖標,那麼可以使用
RaisedButton.icon()
。 FlatButton :扁平化的按鈕,繼承自MaterialButton
OutlineButton :帶邊框的按鈕,繼承自MaterialButton
IconButton :圖標按鈕,繼承自StatelessWidget
FloatingActionButton : 浮動按鈕,繼承自StatelessWidget
按鈕通常情況下是不能直接設置寬高的,如果要設置寬高可是在外層包一個Container組件。也可以在外層包一個Expanded組件使其可以自適應寬度。
常用屬性如下
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
onPressed | VoidCallback | 必傳 | -- | 按下按鈕時觸發的回調方法,傳null表示按鈕禁用,會顯示禁用相關樣式。 |
child | Widget | 是 | -- | 子組件,不傳就無法顯示內容。 |
textColor | Color | 是 | -- | 文本顏色 |
color | Color | 是 | -- | 按鈕的顏色 |
disabledColor | Color | 是 | -- | 按鈕禁用時的顏色 |
disabledTextColor | Color | 是 | -- | 按鈕禁用時的文本顏色 |
splashColor | Color | 是 | -- | 點擊按鈕時水波紋的顏色 |
highlightColor | Color | 是 | -- | 點擊(長按)按鈕後按鈕的顏色 |
elevation | double | 是 | -- | 陰影的範圍,值越大陰影範圍越大 |
padding | EdgeInsetsGeometry | 是 | -- | 內邊距 |
shape | ShapeBorder | 是 | -- | 設置按鈕的形狀 |
minWidth | double | 是 | -- | 最小寬度 |
height | double | 是 | -- | 高度 |
materialTapTargetSize | MaterialTapTargetSize | 是 | MaterialTapTargetSize.padded | 由於按鈕按下後通常會有陰影效果,所以按鈕默認都會有邊距,如果不希望有邊距可以通過該屬性設置,MaterialTapTargetSize.padded:有邊距。MaterialTapTargetSize.shrinkWrap:無邊距。 |
20. Chip 碎片組件
該組件一般用作標籤。
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
avatar | Widget | 是 | -- | 一般用來設置左邊的圖標。 |
label | Widget | 必填 | -- | 一般用來設置文字。 |
deleteIcon | Widget | 是 | -- | 刪除圖標,如果設置了onDeleted回調,即使該參數不設置也會顯示默認圖標。 |
deleteIconColor | Color | 是 | -- | 刪除圖標的顏色。 |
deleteButtonTooltipMessage | String | 是 | delete | 刪除圖標被按壓時的提示文字。 |
shape | ShapeBorder | 是 | 默認爲兩邊是半圓的形狀 | 設置背景形狀。 |
backgroundColor | Color | 是 | -- | 背景顏色。 |
materialTapTargetSize | MaterialTapTargetSize | 是 | MaterialTapTargetSize.padded | 由於按鈕按下後通常會有陰影效果,所以按鈕默認都會有邊距,如果不希望有邊距可以通過該屬性設置,MaterialTapTargetSize.padded:有邊距。MaterialTapTargetSize.shrinkWrap:無邊距。 |
21. BottomNavigationBar 底部導航條組件
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
currentIndex | int | 是 | -- | 當前選中的Tab的索引。 |
items | List<BottomNavigationBarItem> | 必填 | -- | 設置當前有多少個Item。 |
iconSize | double | 是 | 24 | 圖標大小 |
fixedColor | Color | 是 | -- | 選中後圖標及字體的顏色。該屬性通常不需要設置,會默認使用主體顏色。 |
type | BottomNavigationBarType | 是 | -- | BottomNavigationBarType.fixed : 自動將所有item都顯示到屏幕上。 |
22. AppBar 導航欄組件
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
title | Widget | 是 | -- | 標題 |
centerTitle | bool | 是 | false | 標題是否居中顯示 |
actions | List<Widget> | 是 | -- | 右邊菜單 |
leading | Widget | 是 | -- | 左邊的導航圖標 |
23. DefaultTabController 組件(實現類似於Android中的TabLayout效果)
要實現類似Android中的LabLayout效果還需要結合另外兩個組件TabBar
和TabBarView
一起使用,以下是這三個組件的具體參數介紹以及代碼示例:
-
DefaultTabController
參數 類型 可選 默認值 說明 length int 必填 -- 設置有多少個tab。 initialIndex int 是 0 默認選中第幾個tab,索引從0開始。 child Widget 必填 -- child通常是一個Scaffold組件,但是需要有特俗的寫法。下面會給出栗子。 - TabBar
參數 類型 可選 默認值 說明 isScrollable bool 是 false 設置是否開啓滾動,tab過多時建議開啓,否則無法顯示。 tabs List<Widget> 必填 -- 設置tab,具體有多少個tab由集合的長度決定。 controller TabController 是 -- 用於自己定義控制器。 indicatorColor Color 是 -- 設置指示器的顏色,默認會根據主題色做出調整。 indicatorWeight double 是 2.0 設置指示器的高度。 indicatorPadding EdgeInsetsGeometry 是 EdgeInsets.zero 底部指示器的padding, indicator Decoration 是 -- 設置Tab的樣式,例如邊框等。 indicatorSize TabBarIndicatorSize 是 -- 指示器大小的計算方式,TabBarIndicatorSize.label:和文字等寬,TabBarIndicatorSize.tab:和tab等寬。 labelColor Color 是 -- 統一設置標籤字體顏色,默認與指示器顏色一致。 lablStyle TextStyle 是 -- 統一設置選中時標籤字體樣式。 unselectedLabelColor Color 是 -- 設置未選中時標籤字體顏色,默認與指示器顏色一致。 unselectedLabelStyle TextStyle 是 -- 設置未選中時標籤字體樣式。 import 'package:flutter/material.dart'; class MovieDetailPage extends StatelessWidget { final data; List<String> get _movies { return data['movies']; } int get _index { return data['index']; } MovieDetailPage(this.data); @override Widget build(BuildContext context) { return DefaultTabController( // 1.要實現Android中的TabLayout需要使用DefaultTabController組件。 initialIndex: this._index, length: _movies.length, child: Scaffold( // 2.使用Scaffold作爲DefaultTabController組件的child。 appBar: AppBar( // 3.設置appBar。 title: Text('電影專區'), centerTitle: true, bottom: TabBar( // 4.設置bottom屬性爲TabBar組件。 isScrollable: true, // 5.如果tab過多時需要將isScrollable屬性設置爲true,否者無法顯示。默認爲false。 tabs: _movies.map((movie) => Tab(text: movie)).toList() // 6.設置tab的樣式,建議使用Tab組件。 ), ), body: TabBarView( // 7.設置body屬性爲TabBarView組件。 children: _movies.map((movie) { // 8.爲TabBarView設置每個頁面的具體內容(繪製每個頁面)。 return Align( alignment: Alignment.center, child: Text('電影《$movie》的詳情頁面。')); }).toList(), ), ), ); } } //調用 Navigator.push(context, MaterialPageRoute(builder: (context) { return MovieDetailPage({ 'index': 0, 'movies': [ '天龍八部', '別拿村長不當幹部', '長征', '白衣校花大長腿', '死侍', 'X戰警', '007大破天幕殺機', '信條', '射鵰英雄傳', '新白娘子傳奇', '三體', '鹿鼎記', '我和殭屍有個約會', '奇異博士', '蜘蛛俠', '復仇者聯盟-無限戰爭', '復仇者聯盟-逆轉無限' ] }); }));
他還有一種高級用法,可以自己監聽生命週期,並監聽滾動事件等。具體用法如下:
import 'package:flutter/material.dart'; class SuperiorTabControllerPage extends StatefulWidget{ @override State<StatefulWidget> createState() { return _SuperiorTabControllerPageState(); } } class _SuperiorTabControllerPageState extends State<SuperiorTabControllerPage> with SingleTickerProviderStateMixin{ TabController _tabController; @override void initState() { super.initState(); _tabController = TabController( length: 2, vsync: this //1.固定寫法 ); _tabController.addListener(() { print(this._tabController.index); }); } @override void dispose() { super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('TabController的高級用法'), bottom: TabBar( controller: this._tabController, //2.TabBar必須設置自己定義的TabController indicatorSize: TabBarIndicatorSize.label, tabs: [ Tab(text: '科幻'), Tab(text: '懸疑') ], ), ), body: TabBarView( controller: this._tabController, //3.TabBarView必須設置自己定義的TabController children: [ Center( child: Text('科幻'), ), Center( child: Text('懸疑'), ), ], ), ); } } //調用 Navigator.push(context, MaterialPageRoute( builder: (context) => SuperiorTabControllerPage() ))
24. Divider 線條組件
該組件通常用於在一面中繪製一條線。
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
height | double | 是 | -- | 線條的高度 |
color | Color | 是 | -- | 線條的顏色 |
25. ListTile 條目組件
該組件可以快速繪製類似於個人中心頁面中的條目,或則手機設置頁面中的條目。
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
leading | Widget | 是 | -- | 通常用於設置左側的圖標。 |
title | Widget | 是 | -- | 通常用於設置標題。 |
subtitle | Widget | 是 | -- | 通常用於設置子標題。 |
onTap | GestureTapCallback | 是 | -- | 用於監聽點擊事件。 |
26. Drawer 抽屜組件
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
elevation | double | 是 | 16.0 | 設置陰影高度 |
child | Widget | 必填 | -- | 繪製抽屜中的內容 |
在使用Drawer組件的時候我們通常要爲抽屜設置頭,否則會有點難看。如果要設置頭就需要用到一下兩個組件:
-
DrawerHeader
參數 類型 可選 默認值 說明 decoration Decoration 是 -- 用於設置樣式,通常使用BoxDecoration。 margin EdgeInsetsGeometry 是 -- 設置外邊距 padding EdgeInsetsGeometry 是 -- 設置內邊距 child Widget 必填 -- 繪製內容視圖 - UserAccountsDrawerHeader
參數 類型 可選 默認值 說明 accountName Widget 必填 -- 設置用戶名 accountEmail Widget 必填 -- 設置用戶郵箱 currentAccountPicture Widget 是 -- 設置當前用戶頭像 onDetailsPressed VoidCallback 是 -- 用戶信息被點擊後的監聽。 arrowColor Color 是 -- 當設置了onDetailsPressed後右側會出現一個小的三角箭頭,該參數用於設置箭頭顏色。 otherAccountsPictures List<Widget> 是 -- 用於設置其他用戶頭像,設置後會在當前頭像的右側出現小的頭像。 如果需要用代碼關閉抽屜可以使用下面的方式:
Navigator.pop(context)
27. TextField 文本框組件
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
decoration | InputDecoration | 是 | InputDecoration() | 設置邊框、hint、lable、圖標等 |
obscureText | bool | 是 | false | 是否隱藏明文顯示內容(密碼模式)。 |
obscuringCharacter | String | 是 | -- | 當obscureText爲true時,用於設置顯示的文字。 |
maxLines | int | 是 | 1 | 最大行數,當設置大於1時爲多行輸入模式。 |
minLines | int | 是 | -- | 最小輸入行數,主要是用來設置最小高度。 |
controller | TextEditingController | 是 | -- | 用於設置默認顯示內容以及獲取編輯框中的內容。 |
onChanged | ValueChanged<String> | 是 | -- | 監聽內容的改變。 |
onEditingComplete | VoidCallback | 是 | -- | 監聽焦點的丟失(編輯完成) |
28. CheckBox 多選框組件
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
value | bool | 必填 | -- | 設置是否選中 |
onChanged | ValueChanged<bool> | 必填 | -- | 監聽選中狀態的改變 |
29. CheckboxListTile組件
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
value | bool | 必填 | -- | 設置是否選中 |
onChanged | ValueChanged<bool> | 必填 | -- | 監聽選中狀態的改變 |
title | Widget | 是 | -- | 通常用於設置標題。 |
subtitle | Widget | 是 | -- | 通常用於設置子標題。 |
secondary | Widget | 是 | -- | 配置圖標或者圖片。 |
selected | bool | 是 | false | 選中的時候文字是否跟着改變。 |
30. Radio 單選框組件
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
value | T | 必填 | -- | 設置當前單選框所代表的值。 |
onChanged | ValueChanged<T> | 必填 | -- | 監聽選中狀態的改變,並將當前單選框所代表的值(T)回傳。 |
groupValue | T | 是 | -- | 爲當前單選框分組。 |
31. RadioListTile組件
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
value | T | 必填 | -- | 設置當前單選框所代表的值。 |
onChanged | ValueChanged<T> | 必填 | -- | 監聽選中狀態的改變,並將當前單選框所代表的值(T)回傳。 |
title | Widget | 是 | -- | 通常用於設置標題。 |
subtitle | Widget | 是 | -- | 通常用於設置子標題。 |
secondary | Widget | 是 | -- | 配置圖標或者圖片。 |
selected | bool | 是 | false | 選中的時候文字是否跟着改變。 |
groupValue | T | 是 | -- | 爲當前單選框分組。 |
32. Switch 開關組件
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
value | bool | 必填 | -- | 設置是否選中 |
onChanged | ValueChanged<bool> | 必填 | -- | 監聽選中狀態的改變 |
33. 日期組件以及日期和時間戳
-
日期和時間戳
日期轉化成時間戳:
print(DateTime.now().millisecondsSinceEpoch) //輸出:1600925003761
時間戳轉換成日期:
print(DateTime.fromMillisecondsSinceEpoch(1600925003761)) //輸出:2020-09-24 13:23:23:761
-
使用系統的日期選擇組件:
showDatePicker( context: context, initialDate: DateTime.now(), //默認選中的日期 firstDate: DateTime(2019), //開始(最早)日期 lastDate: DateTime(2050) //結束(最晚)日期 ).then((value) => setState(() => date = value)); //showDatePicker方法返回的是Future<DateTime>類型,所以這裏使用then方法接收結果。但也可以使用async結合await的方式。
-
使用系統的時間選擇組件:
showTimePicker(context: context, initialTime: TimeOfDay.now()) .then((value) => setState(() => time = value.format(context))); //showTimePicker方法返回的是Future<TimeOfDay>類型,所以這裏使用then方法接收結果。但也可以使用async結合await的方式。
四、第三方組件庫
雖然Flutter爲我們提供了很多的Widget,但有時Flutter爲我們提供的組件無法滿足我們需求,這時我們就需要使用一些第三方的Widget組件。我們可以通過網站pub.dev來找到我們想要的Widget組件進行使用。
1. Toast 組件
-
使用:
-
在pubspec.yaml文件中的dependencies節點下添加toast: ^版本號:
dependencies: flutter: sdk: flutter #Toast toast: ^0.1.5 #例如這裏添加0.1.5版本
-
在代碼中使用:
import 'package:toast/toast.dart' Toast.show('我是一個Toast', context, gravity: Toast.CENTER);
-
2. Swiper 輪播圖組件
-
使用:
-
在pubspec.yaml文件中的dependencies節點下添加flutter_swiper: ^版本號:
dependencies: flutter: sdk: flutter #輪播圖組件 flutter_swiper: ^1.1.6 #例如這裏添加1.1.6版本
-
在代碼中使用:
import 'package:flutter_swiper/flutter_swiper.dart'; Swiper( itemCount: images.length, //設置一共有多少個item itemBuilder: (context, index) { //構建沒給item return Container( child: Image.network(images[index], fit: BoxFit.cover), decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), ), clipBehavior: Clip.antiAlias, ); }, pagination: SwiperPagination(),//顯示指示器 autoplay: true, //是否自動播放 viewportFraction: 0.8, //當前頁的佔比,如果小於1,那麼剩下的部分將有左右兩邊填充。 scale: 0.9, //每個Banner的縮放比例 onTap: (i) => setState(()=> _currentIndex = i), //條目點擊監聽 )
-
3. DateFormat 日期格式化組件
-
在代碼中使用:
-
在pubspec.yaml文件中的dependencies節點下添加date_format: ^版本號:
dependencies: flutter: sdk: flutter #日期格式化 date_format: ^1.0.9 #例如這裏添加1.0.9版本
-
使用:
由於該庫使用起來稍微有點麻煩,所以我對他進行了封裝(可能不是很完整,只是提供思路)。
import 'package:date_format/date_format.dart' as df; String formatDate(DateTime date, DateType type){ return df.formatDate(date, _getFormat(type)); } _getFormat(DateType type){ switch (type) { case DateType.yyyy_MM_dd: return ['yyyy', '-', 'mm', '-', 'dd']; case DateType.yyyy_MM_dd_HH: return ['yyyy', '-', 'mm', '-', 'dd', ' ', 'HH']; case DateType.yyyy_MM_dd_HH_mm: return ['yyyy', '-', 'mm', '-', 'dd', ' ', 'HH', ':', 'nn']; case DateType.yyyy_MM_dd_HH_mm_ss: return ['yyyy', '-', 'mm', '-', 'dd', ' ', 'HH', ':', 'nn', ":", 'ss']; case DateType.HH_mm_ss: return ['HH', ':', 'nn', ":", 'ss']; case DateType.HH_mm: return ['HH', ':', 'nn']; } } enum DateType{ yyyy_MM_dd, yyyy_MM_dd_HH, yyyy_MM_dd_HH_mm, yyyy_MM_dd_HH_mm_ss, HH_mm, HH_mm_ss } //使用方式如下 formatDate(date, DateType.yyyy_MM_dd)
-
五、Dialog 彈窗
如果要使用AlertDialog則需要使用showDialog(context, builder)
方法。該方法有兩個核心參數如下:
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
context | BuildContext | 必填 | -- | build方法中的上下文。 |
builder | Widget Function(BuildContext context) | 必填 | -- | 構建Dialog的回調函數。 |
第一個參數context
比較簡單,只要將Widget的build方法中的context或則State中的context成員傳入即可。第二個參數builder
則是一個Function。你需要定義一個Function並返回你創建好的Widget,這個Widget可以是以下幾種:
1. AlertDialog
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
title | Widget | 是 | -- | 設置標題 |
titlePadding | EdgeInsetsGeometry | 是 | -- | 標題的內邊距 |
titleTextStyle | TextStyle | 是 | -- | 標題的字體樣式 |
content | Widget | 是 | -- | 設置內容 |
contentPadding | EdgeInsetsGeometry | 是 | EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 24.0) | 內容的內邊距 |
contentTextStyle | TextStyle | 是 | -- | 內容的字體樣式 |
actions | List<Widget> | 是 | -- | 設置按鈕 |
showDialog(
context: context,
builder: (context){
return AlertDialog(
title: Text('提示'),
content: Text('您覺的這個Demo對您的幫助大嗎?'),
actions: [
FlatButton(
child: Text('是的'),
onPressed: () {
Navigator.pop(context);
Toast.show(context, '謝謝您的認可~');
},
),
FlatButton(
child: Text('非常大'),
onPressed: () {
Navigator.pop(context);
Toast.show(context, '您真是一個好人吶~');
},
)
],
);
}
);
有些情況下我們需要獲取Dialog操作後的返回值,這時可以分爲以下幾步:
- 自定義函數上加上
async
關鍵字。 - 在
showDialog
方法前面加上await
關鍵字並定義一個變量接收該方法的返回值。 - Navigator.pop()退出彈窗時傳入要返回的值。
- 拿到showDialog方法的返回結果做相應處理。
_showAlertDialog() async{ //1.加上async關鍵字
var result = await showDialog( //2.加上await關鍵字並用變量接收
context: context,
builder: (context){
return AlertDialog(
title: Text('提示'),
content: Text('您覺的這個Demo對您的幫助大嗎?'),
actions: [
FlatButton(
child: Text('是的'),
onPressed: () {
Navigator.pop(context, '您點擊了按鈕:是的'); //3.退出彈出時傳入要返回的值。
Toast.show(context, '謝謝您的認可~');
},
),
FlatButton(
child: Text('非常大'),
onPressed: () {
Navigator.pop(context, '您點擊了按鈕:非常大'); //3.退出彈出時傳入要返回的值。
Toast.show(context, '您真是一個好人吶~');
},
)
],
);
}
);
print(result); //4.拿到showDialog方法的返回結果做相應處理。
}
2. SimpleDialog
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
title | Widget | 是 | -- | 設置標題 |
titlePadding | EdgeInsetsGeometry | 是 | EdgeInsets.fromLTRB(24.0, 24.0, 24.0, 0.0) | 標題的內邊距 |
titleTextStyle | TextStyle | 是 | -- | 標題的字體樣式 |
children | List<Widget> | 是 | -- | 設置內容,通常使用SimpleDialogOption作爲元素。 |
contentPadding | EdgeInsetsGeometry | 是 | EdgeInsets.fromLTRB(0.0, 12.0, 0.0, 16.0) | 內容的內邊距 |
backgroundColor | Color | 是 | -- | 設置背景顏色 |
_showSimpleDialog() async{
var result = await showDialog(
context: context,
builder: (context){
return SimpleDialog(
title: Center(
child: Text('請您選擇'),
),
children:[
SimpleDialogOption(
child: Text('第一個選項'),
onPressed: (){
Navigator.pop(context, '您點擊了: 第一個選項');
},
),
Divider(height: 0),
SimpleDialogOption(
child: Text('第二個選項'),
onPressed: (){
Navigator.pop(context, '您點擊了: 第二個選項');
},
),
Divider(height: 0),
SimpleDialogOption(
child: Text('第三個選項'),
onPressed: (){
Navigator.pop(context, '您點擊了: 第三個選項');
},
)
],
);
}
);
print(result??'您取消了選擇');
}
3. BottomSheet
如果要使用BottomSheet則需要使用showModalBottomSheet(context, builder)
方法。該方法有兩個核心參數如下:
參數 | 類型 | 可選 | 默認值 | 說明 |
---|---|---|---|---|
context | BuildContext | 必填 | -- | build方法中的上下文。 |
builder | Widget Function(BuildContext context) | 必填 | -- | 構建你的自定義佈局。 |
_showBottomSheet() async{
var result = await showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) {
return Container(
height: 290,
child: Column(
children: [
ListTile(
title: Text('第一個條目'),
onTap: () => Navigator.pop(context, '第一個條目'),
),
Divider(height: 0),
ListTile(
title: Text('第二個條目'),
onTap: () => Navigator.pop(context, '第二個條目'),
),
Divider(height: 0),
ListTile(
title: Text('第三個條目'),
onTap: () => Navigator.pop(context, '第三個條目'),
),
Divider(height: 0),
ListTile(
title: Text('第四個條目'),
onTap: () => Navigator.pop(context, '第四個條目'),
),
Divider(height: 0),
ListTile(
title: Text('第五個條目'),
onTap: () => Navigator.pop(context, '第五個條目'),
),
],
),
);
}
);
print('您點擊了:$result');
}
4. 自定義Dialog
在Flutter中,如果要自定義Dialog,需要以下幾個步驟:
- 聲明一個類並繼承自Dialog。
- 重寫Dialog中的
Widget build(BuildContext context)
方法。 - 在
Widget build(BuildContext context)
方法中返回的Widget必須以Material組件作爲根節點。 - 通常情況下我們的Dialog都是有透明背景的,就是Dialog彈出後仍然能看到一部分原來的頁面,所以我們需要給Material組件設置type屬性爲:
MaterialType.transparency
。
下面的栗子就是自定義一個Dialog
class CustomDialog extends Dialog {
@override
Widget build(BuildContext context) {
return Material(
type: MaterialType.transparency,
child: Stack( //從這裏開始就可以寫我們自己想要的界面了。
alignment: Alignment.center,
children: [
Container(
margin: EdgeInsets.only(left: 40, right: 40),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(8),
topRight: Radius.circular(8))),
child: Column(
children: [
SizedBox(height: 12),
Text('提示',
style: TextStyle(
fontWeight: FontWeight.bold, fontSize: 15)),
SizedBox(height: 12),
Divider(height: 0),
],
),
),
Container(
padding: EdgeInsets.all(12),
color: Colors.white,
constraints: BoxConstraints(minHeight: 100),
child: Center(
child: Text('這是一個自定義彈出,你知道了嗎?'),
),
),
Divider(height: 0),
Row(
children: [
Expanded(
child: Container(
height: 50,
child: FlatButton(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(8))),
child: Text('不知道'),
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
onPressed: () {
Navigator.pop(context, '我看你該捱揍了。');
},
),
),
),
VerticalDivider(width: 0.1),
Expanded(
child: Container(
height: 50,
child: FlatButton(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(8))),
child: Text('知道了',
style: TextStyle(color: Colors.blue)),
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
onPressed: () {
Navigator.pop(context, '不錯,你很聰明。');
},
),
),
)
],
)
],
),
)
],
),
);
}
}
使用上面的自定義Dialog和普通的Dialog沒有什麼區別,下面是使用時的代碼:
_showCustomDialog() async {
var result = await showDialog(
context: context,
builder: (context) {
return CustomDialog();
});
print(result);
}
如果要實現打開彈窗後的一段時間之後自動關閉彈窗則可以結合Dart中的Timer定時器實現:
Timer.periodic(Duration(seconds: 5), (timer) {
timer.cancel();
//do something,such as dismiss the dialog.
});
六、註解
1. @override 重寫
該註解用於表示一個成員是繼承自父類。
2. @protected
該註解只能應用於一個類中的成員,表示該成員只能被子類調用或擴展,也可以在混合類中直接或間接的調用。
3. @mustCallSuper
該註解應用在方法上,表示該方法如果被子類重寫了,那麼子類則必須調用super。
七、路由
Flutter中路由的核心類爲Navigator,如果要打開新的頁面則需要調用push
方法,如果要退出頁面則需要調用pop
方法。
1. 普通路由
Navigator.push(context, MaterialPageRoute(
builder:(context){
return SecondPage(); //這裏返回目標頁面。
}
));
2. 普通路由傳值
普通路由的傳值方式其實就是在構造要跳轉的頁面時將參數傳入。
Navigator.push(context, MaterialPageRoute(
builder:(context){
return SecondPage(title: '第二個頁面'); //這裏構建要跳轉的頁面時直接傳入參數。
}
));
class SecondPage extends StatelessWidget{
final String title;
SecondPage({this.title = ""}); //這裏接受外面傳入的參數
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.title), //這裏使用外面傳入的參數
),
body: Align(
alignment: Alignment.center,
child: Text("我是${this.title}"), //這裏使用外面傳入的參數
),
);
}
}
3. 命名路由
命名路由的使用分以下兩步:
-
在主入口中的MaterialApp中配置
routes
,routes
接受的數據類型時Map,Map的key是路由命名,值是初始具體跳轉到某個頁面的回調。void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( theme: ThemeData( primarySwatch: Colors.blue, ), home: Tabs(), routes: { //使用routes參數配置命名路由。 '/second': (context) => SecondPage() //這裏定義了一個命名路由‘/second’並制定跳轉到SecondPage頁面。 }, ); } }
-
利用
pushNamed
方法使用已經定義好的路由。Navigator.pushNamed(context, '/second'); //這裏的‘/second’就是我們已經定義好的路由,必須和已經定義好的名稱保持一致。
4. 命名路由傳值
命名路由的傳值略微有點複雜,具體代碼如下:
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
//將我們所有的路由定義爲成員變量。
final routes = {
'/second': (context, {arguments}) => SecondPage(title: arguments['title'])
};
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Tabs(),
onGenerateRoute: (RouteSettings settings){ //利用onGenerateRoute自己處理傳參及跳轉。
final String name = settings.name; //獲取路由名稱
final Function pageContentBuilder = this.routes[name]; //根據名稱獲取具體跳轉的方法
if(pageContentBuilder != null) { //判斷方法是否爲空,如果空則拋出異常。
return MaterialPageRoute(
builder: (context) {
if(settings.arguments != null) { //如果有參數則調用我們自定義的方法傳入參數
return pageContentBuilder(context, arguments: settings.arguments);
}else {
return pageContentBuilder(context); //沒有參數時則忽略arguments
}
}
);
}else {
throw ArgumentError('route "$name" not implements!');
}
},
);
}
}
路由已經定義好了,那麼調用的方式如下:
Navigator.pushNamed(context, '/second', arguments: {
'title': '第二個頁面'
});
代碼抽離:
-
入口文件
import 'package:flutter/material.dart'; import 'package:flutter_demo_one/pages/route.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( theme: ThemeData( primarySwatch: Colors.blue, ), initialRoute: '/', onGenerateRoute: generateRoute, ); } }
-
Route文件
import 'package:flutter/material.dart'; import 'package:flutter_demo_one/pages/hospital/hospitalDetailPage.dart'; import 'pages/main/tabs.dart'; final routes = { '/': (context) => Tabs(), '/hospital': (context, {arguments}) => HospitalDetailPage(arguments) }; Function generateRoute = (RouteSettings settings){ final String name = settings.name; final Function pageContentBuilder = routes[name]; if(pageContentBuilder != null) { return MaterialPageRoute( builder: (context) { if(settings.arguments != null) { return pageContentBuilder(context, arguments: settings.arguments); }else { return pageContentBuilder(context); } } ); }else { throw ArgumentError('route "$name" not implements!'); } };
5. 替換路由
替換路由其實就是用要打開的新的頁面來替換當前頁面,例如:A是第一頁面(主頁面),A正常打開了B頁面,而B又用替換路由打開了C,那麼此時的頁面棧中就只有AC兩個頁面,如果在C頁面中點擊返回按鈕後( Navigator.pop()
)就會直接顯示A而不會顯示B。
注意:使用替換路由時,被打開的頁面是否有返回按鈕(左上角的返回箭頭)取決於當前頁面是否有返回按鈕。
Navigator.pushReplacementNamed(context, '/testPage2', arguments: {
'title':'頁面標題'
});
八、 網絡請求
1. Json 與 Map 相互轉換。
-
Json轉Map
final String _json = '{"username":"張三","age":23}'; //json字符串數據 print(json.decode(_json)['username']) //輸出:張三
-
Map轉Json
final Map _map = { "username":"張三", "age": 23 }; print(context, json.encode(_map)) //輸出:{"username":"張三","age":23}
2. 使用http庫進行網絡請求
-
使用:
-
在pubspec.yaml文件中的dependencies節點下添加http: ^版本號:
dependencies: http: ^0.12.2
-
在代碼中使用:
get請求
import 'package:http/http.dart'; _onGetData(apiUrl) async{ var result = await get(apiUrl); if(result.statusCode == 200) { print(result.body); }else { print('接口請求錯誤!'); } }
post請求
_doLogin(context, username, psw, {url = ''}) async { var result = await post(url, body: {'username':username, 'psw':psw}); if (result.statusCode == 200) { print(result.body); } else { print('接口請求錯誤!'); } }
-
3. dio 庫請求
dio是一個強大的Dart Http請求庫,支持Restful API、FormData、攔截器、請求取消、Cookie管理、文件上傳/下載、超時、自定義適配器等...
地址:https://pub.dev/packages/dio github地址:https://github.com/flutterchina/dio
-
使用:
-
在pubspec.yaml文件中的dependencies節點下添加dio: ^版本號:
dependencies: dio: ^3.0.7
-
簡單使用
import 'package:dio/dio.dart'; void getHttp() async { try { Response response = await Dio().get("http://www.baidu.com"); print(response); } catch (e) { print(e); } }
-
發起一個
GET
請求 :Response response; Dio dio = Dio(); response = await dio.get("/test?id=12&name=wendu") print(response.data.toString()); // 請求參數也可以通過對象傳遞,上面的代碼等同於: response = await dio.get("/test", queryParameters: {"id": 12, "name": "wendu"}); print(response.data.toString());
-
發起一個
POST
請求:response = await dio.post("/test", data: {"id": 12, "name": "wendu"});
-
發起多個併發請求:
response = await Future.wait([dio.post("/info"), dio.get("/token")]);
-
下載文件:
response = await dio.download("https://www.google.com/", "./xx.html");
-
以流的方式接收響應數據:
Response<ResponseBody> rs = await Dio().get<ResponseBody>(url, options: Options(responseType: ResponseType.stream), //設置接收類型爲stream ); print(rs.data.stream); //響應流
-
以二進制數組的方式接收響應數據:
Response<List<int>> rs = await Dio().get<List<int>>(url, options: Options(responseType: ResponseType.bytes), //設置接收類型爲bytes ); print(rs.data); //二進制數組
-
發送 FormData:
FormData formData = FormData.from({ "name": "wendux", "age": 25, }); response = await dio.post("/info", data: formData);
-
通過FormData上傳多個文件:
FormData.fromMap({ "name": "wendux", "age": 25, "file": await MultipartFile.fromFile("./text.txt",filename: "upload.txt"), "files": [ await MultipartFile.fromFile("./text1.txt", filename: "text1.txt"), await MultipartFile.fromFile("./text2.txt", filename: "text2.txt"), ] }); response = await dio.post("/info", data: formData);
-
監聽發送(上傳)數據進度:
response = await dio.post( "http://www.dtworkroom.com/doris/1/2.0.0/test", data: {"aa": "bb" * 22}, onSendProgress: (int sent, int total) { print("$sent $total"); }, );
-
以流的形式提交二進制數據:
// 二進制數據 List<int> postData = <int>[...]; await dio.post( url, data: Stream.fromIterable(postData.map((e) => [e])), //創建一個Stream<List<int>> options: Options( headers: { Headers.contentLengthHeader: postData.length, // 設置content-length }, ), );
注意:如果要監聽提交進度,則必須設置content-length,反之則是可選的。
-
九、其他
1. 國際化
-
第一步:找到pubspec.yaml文件,配置flutter_localizations(國際化)。
dependencies: flutter: sdk: flutter #國際化配置 flutter_localizations: sdk: flutter
-
第二步:導入國際化的包flutter_localizations(AS可以自動導包)。
import 'package:flutter_localizations/flutter_localizations.dart';
-
第三步:在main入口中的MaterialApp組件中配置國際化:
import 'package:flutter/material.dart'; import 'package:flutter_demo_one/route.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( theme: ThemeData( primarySwatch: Colors.blue ), initialRoute: '/', onGenerateRoute: generateRoute, //以下是國際化配置,配置下面localizationsDelegates和supportedLocales兩個屬性即可實現國際化。 localizationsDelegates: [ GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate ], supportedLocales: [ const Locale('zh', 'CH'), const Locale('en', 'US') ], ); } }
2. 沉浸式狀態欄
只需要在入口處build方法中return前加入下面一行代碼,將狀態欄的顏色設置爲透明色即可:
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(statusBarColor: Colors.transparent));