如何啟動
啟用安卓模擬器
$ emulator -list-avds // 返回可用安卓模擬器列表
$ emulator @AVD_name // 啟動某個模擬設備
啟用 ios 模擬器
$ open -a simulator // 直接打開xcode 模擬器
啟動項目
$ flutter devices // 返回可用模擬器列表
$ flutter run -d 模擬器xxx // 啟用id為xxx的模擬器
熱更新啟動方式
$ flutter run -d 模擬器xxx --hot // 啟用id為xxx的模擬器
不能使用 Hot Reload 的場景
- 代碼出現編譯錯誤的不能使用 Hot Reload
- 代碼更改會影響 APP 狀態(tài)的不能使用 Hot Reload
- 全局變量( global variables)和靜態(tài)字段(static fields)的更改不能使用 Hot Reload
// 如下的代碼:
final sampleTable = [
Table("T3"),
];
/// 運行 App 之后倡缠,如果做了如下的更改:
final sampleTable = [
Table("T3"),
Table("T10"), // 修改這里的值
];
- main() 方法里的更改不能使用 Hot Reload
- 枚舉類型更改為常規(guī)的類或者常規(guī)的類變?yōu)槊杜e類型也不能使用 HotReload
- 修改通用類型聲明也不能使用 HotReload
// 例如拂檩,如下的例子:
class A<T> {
T i;
}
// 改為:
class A<T, V> {
T i;
V v;
}
如何定義 model(6 步)
JSON 和序列化
// 1.導入庫
import 'package:json_annotation/json_annotation.dart';
// 在Dart中诀艰,可以在同一庫中訪問私有成員。 使用import可以導入庫,并且只能訪問其公共成員。 使用part/part of,可以將一個庫分成幾個文件便瑟,并且私有成員可訪問這些文件中的所有代碼。
// 2. part xx.g.dart庫番川,將在我們運行生成命令后自動生成
part 'tag.g.dart';
/// 3.裝飾器引用到涂,這個標注是告訴生成器,這個類是需要生成Model類的
@JsonSerializable()
/// 4.定義類
class Tag {
String id;
String tagName;
Tag(
this.id,
this.tagName,
);
/// 5.定義 方法名為 類名.fromJson和_$類名ToJson 方法爽彤,格式如下
factory Tag.fromJson(Map<String, dynamic> json) => _$TagFromJson(json);
Map<String, dynamic> toJson() => _$TagToJson(this);
/// 6.執(zhí)行命令生成json序列化代碼
/// $ flutter packages pub run build_runner build
}
flutter packages pub run build_runner watch
在項目根目錄下運行來啟動watcher养盗。只需啟動一次觀察器,然后并讓它在后臺運行适篙,這是安全的
相關部件(Widget)使用說明
一往核、Scaffold 使用
Scaffold({
this.appBar, // 頂部導航
this.body, // 主體
this.drawer, // 頁面左抽屜,從左往右抽出
this.endDrawer, // 頁面右抽屜嚷节,從右往左抽出
this.bottomNavigationBar, // 底部導航
this.backgroundColor, // 背景色
})
二聂儒、AppBar 使用及擴展
AppBar 設計上涵蓋了左中右下四個方向的占位,對應的屬性分別是 leading, title, actions, bottom硫痰,leading衩婚,actions 帶有默認 padding 和 margin 值
AppBar({
this.leading, // Widget
this.automaticallyImplyLeading = true, // bool 路由棧存在上一個路由的情況下,leading為返回箭頭
this.title, // Widget效斑,通常是Text
this.actions, // List<Widget>
this.bottom, // PreferredSizeWidget非春,通常是TabBar
this.elevation, // double 高度陰影
this.backgroundColor, // Color
this.centerTitle, // bool 居中標題
...
})
定制 AppBar
首頁 AppBar 模塊 bottom 部分組成是 Tabbar + 頻道選擇,非 PreferredSizeWidget 類型的部件缓屠,實現方式是定制一個 PreferredSizeWidget奇昙,實現抽象類未實現的方法,在實現 build 的部分復合 Tabbar 和定制化的部分
class ZnTabBar extends StatefulWidget implements PreferredSizeWidget {
/// Creates a [Size] with the given [height] and an infinite [width].
@override
Size get preferredSize {
return Size.fromHeight(/* ZnTabBar所依賴的高度 */);
}
/// ...
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Expanded(child: TabBar()),
CustomPart() // 定制化部分
)
}
三敌完、使用 GestureDetector
GestureDetector 是一個用于手勢識別的功能性組件储耐,通過它可以識別各種手勢。GestureDetector 實際上是指針事件的語義化封裝滨溉;
使用場景
1什湘、類似 Text、TextField 等部件沒有監(jiān)聽點擊的事件晦攒;
2闽撤、扁平化交互,Material 組件庫中提供了多種按鈕組件如 RaisedButton脯颜、FlatButton哟旗、OutlineButton 等,它們都是直接或間接對 RawMaterialButton 組件的包裝定制,Material 庫中的按鈕都有如下相同點:
- 按下時都會有“水波動畫”
- 有一個 onPressed 屬性來設置點擊回調热幔,當按鈕按下時會執(zhí)行該回調,如果不提供該回調則按鈕會處于禁用狀態(tài)讼庇,禁用狀態(tài)不響應用戶點擊绎巨;
GestureDetector(
child: Text('點擊'),
onTap: () {
// 處理點擊
}
)
其它,如實現雙擊蠕啄、長按场勤、拖動、滑動等
GestureDetector({
this.onDoubleTap, // 雙擊
this.onLongPress, // 長按
this.onVerticalDragStart,// 垂直拖動
this.onHorizontalDragStart, // 水平搬動
this.onPanStart, // 滑動
this.onScaleStart, // 縮放
...
四歼跟、使用 SingleChildScrollView
接收一個子組件和媳,當顯示內容超過屏幕且不超出太多的情況下使用,解決內容超出可視區(qū)域產生邊界溢出的情況哈街。
使用場景
1留瞳、線性布局下,Column 接收一組部件骚秦,但內容超出屏高產生垂直邊界溢出的情況下她倘;
2、流式布局下作箍,Row 接收一組部件硬梁,當內容超出屏寬且不折行的情況下;(可使用 Wrap 產生折行)
3胞得、期望的內容不會超過屏幕太多時荧止,視口不會包含超出屏幕尺寸太多的內容時;(可使用 ListView 或 CustomScrollView 代替)
五阶剑、使用 GridView
GridView.count
GridView.count 是 GridView 的命名構造函數跃巡,內部使用了 SliverGridDelegateWithFixedCrossAxisCount,通過它可以快速的創(chuàng)建橫軸固定數量子元素的 GridView
GridView.count(
crossAxisCount: 2, // 橫軸子元素的數量
crossAxisSpacing: 1.0, // 橫軸方向子元素的間距
mainAxisSpacing: 1.0, // 主軸方向的間距
children: buildGridList(),
primary: true // 如果為 true个扰,即使?jié)L動視圖沒有足夠的內容滾動視圖也是可滾動的瓷炮,默認為 false。
shrinkWrap: true, // shrinkWrap 常用于內容大小不確定情況递宅,如果是無界約束娘香,則 shrinkWrap 必須為 true。
)
六办龄、CustomScrollView
CustomScrollView 是可以使用 Sliver 來自定義滾動模型(效果)的組件烘绽,這里 Sliver 通常指可滾動組件子元素。
使用場景
1俐填、包含 類似 GridView 和 ListView 交互安接,同時要求整個頁面的滑動效果是統(tǒng)一的;
2、需要結合 FlexibleSpaceBar 實現 Material Design 中頭部伸縮英融;
CustomScrollView(
controller: _scrollController,
slivers: <Widget>[
// AppBar盏檐,包含一個導航欄
SliverAppBar(
title: Text(...) // Widget
),
// GridView
SliverGrid(
/// 配置同GridView
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 250.0,
mainAxisSpacing: 1.0,
crossAxisSpacing: 1.0,
childAspectRatio: 1.0,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
// 返回Widget
},
childCount: ..., // 列表長度
),
),
//List
new SliverFixedExtentList(
itemExtent: 50.0,
delegate: new SliverChildBuilderDelegate(
(BuildContext context, int index) {
//創(chuàng)建列表項
return new Container(
alignment: Alignment.center,
color: Colors.lightBlue[100 * (index % 9)],
child: new Text('list item $index'),
);
},
childCount: 50 //50個列表項
),
),
)
七歇式、動畫
1、繼承 SingleTickerProviderStateMixin 和 TickerProviderStateMixin胡野,通過將 SingleTickerProviderStateMixin 添加到類定義中材失,可以將 stateful 對象作為 vsync 的值;
2硫豆、兩個對象:Animation 和 AnimationController龙巨;
3、Animation 生成指導動畫的值熊响,AnimationController 管理 Animation旨别;
4、當創(chuàng)建一個 AnimationController 時汗茄,需要傳遞一個 vsync 參數秸弛,存在 vsync 時會防止屏幕外動畫消耗不必要的資源;
5洪碳、使用 Listeners 和 StatusListeners 監(jiān)聽動畫狀態(tài)改變胆屿;
6、釋放資源
vsync 做了什么事偶宫?
vsync 對象會綁定動畫的定時器到一個可視的 widget非迹,所以當 widget 不顯示時,動畫定時器將會暫停纯趋,當 widget 再次顯示時憎兽,動畫定時器重新恢復執(zhí)行,這樣就可以避免動畫相關 UI 不在當前屏幕時消耗資源吵冒。
使用
/// 1.定義類
class ZNAnimation extends StatefulWidget {
static final String routeName = 'animaton';
@override
_ZNAnimation createState() => _ZNAnimation();
}
class _ZNAnimation extends State<ZNAnimation> with SingleTickerProviderStateMixin {
}
/// 2.聲明
AnimationController controller;
Animation<double> animation;
/// 3.初始化并設置動畫監(jiān)聽
@override
initState() {
super.initState();
controller = AnimationController(
duration: const Duration(milliseconds: 100), vsync: this);
// 圖片寬高從0變到300
animation = Tween(begin: 30.0, end: 25.0).animate(controller);
animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
// 動畫執(zhí)行結束時反向執(zhí)行動畫
controller.reverse();
} else if (status == AnimationStatus.dismissed) {
// 動畫恢復到初始狀態(tài)時執(zhí)行動畫(正向)
setState(() {
...
});
}
});
}
/// 4. 使用
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: animation,
builder: (BuildContext context, Widget child) {
return new Container(
height: animation.value,
width: animation.value,
child: xxx, // 接收外部child再由AnimatedBuilder返回并插入widget樹中
);
},
child: xxx // 外部Widget對象
)
}
/// 5.釋放資源
```dart
@override
dispose() {
//路由銷毀時需要釋放動畫資源
controller.dispose();
super.dispose();
}
八纯命、其它部件
1、底部彈窗 showModalBottomSheet
痹栖,
2亿汞、尺寸限制類容器 ConstrainedBox
,
3揪阿、裝飾容器 DecorateBox
可以在其子組件繪制前(或后)繪制一些裝飾(Decoration)疗我,如背景、邊框南捂、漸變等吴裤。
4、剪裁 ClipRRect
將子組件剪裁為圓角矩形
Effective Dart 及項目開發(fā)約定
一溺健、dart 規(guī)范補充
1麦牺、要 盡可能的使用集合字面量來定義集合。
// 好的范例
var points = [];
var addresses = {};
// 壞的范例
var points = new List();
var addresses = new Map();
如果有必要還可以提供泛型類型。
// 好的范例
var points = <Point>[];
var addresses = <String, Address>{};
// 壞的范例
var points = new List<Point>();
var addresses = new Map<String, Address>();
2剖膳、不要 使用 .length 來判斷集合是否為空魏颓。
Dart 提供了更加高效率和易用的 getter 函數: .isEmpty 和.isNotEmpty。使用這些函數并不需要對結果再次取非吱晒。
/// 好的范例
if (lunchBox.isEmpty) return 'so hungry...';
if (words.isNotEmpty) return words.join(' ');
/// 壞的范例
if (lunchBox.length == 0) return 'so hungry...';
if (!words.isEmpty) return words.join(' ');
3琼开、使用 map().toList() 或者 map().toSet() 來 強制立刻執(zhí)行 map 的方法
map() 函數返回的對象也是一個 Iterable,該對象是懶求值(lazily evaluated) 的枕荞,只有當訪問里面的值的時候, map 的方法才被調用搞动。
4躏精、普通方法使用位置參數定義默認值
queryCommentList(String videoId, [int pageSize = 15, int pageNo = 1]) {
// ...
}
// 調用
queryCommentList('videoId');
5、類構造方法使用命名參數
class ZNVideoPlay extends StatefulWidget {
final Video video;
ZNVideoPlay({this.video});
@override
State<StatefulWidget> createState() => _VideoPlay();
}
// 調用
ZNVideoPlay(video: widget.video),
二鹦肿、文件或庫引入方式
直接使用庫導出對象矗烛,Flutter 編輯器插件自動導入庫絕對路徑;
組件通信的兩種方式:
一箩溃、InheritedWidget
使用(3 步)
// 1瞭吃、定義數據存儲部件
class ShareDataWidget extends InheritedWidget {
ShareDataWidget({@required this.data, Widget child}) : super(child: child);
final String data; //需要在子樹中共享的數據,保存點擊次數
//定義一個便捷方法涣旨,方便子樹中的widget獲取共享數據
static ShareDataWidget of(BuildContext context) {
return context.inheritFromWidgetOfExactType(ShareDataWidget);
}
//該回調決定當data發(fā)生變化時歪架,是否通知子樹中依賴data的Widget
@override
bool updateShouldNotify(ShareDataWidget old) {
//如果返回true,則子樹中依賴(build函數中有調用)本widget
//的子widget的`state.didChangeDependencies`會被調用
return old.data != data;
}
}
// 2霹陡、父Widget使用ShareDataWidget包裝子Widget
ShareDataWidget(
data: tagId,
child: VideoList(),
))
// 3和蚪、子Widget使用并監(jiān)聽
@override
void didChangeDependencies() {
_initData();
super.didChangeDependencies();
}
_initData() async {
var tagId = ShareDataWidget.of(context).data;
// Fetch data with tagId
}