萬事皆 Widget
Widget 是每個 Flutter 應(yīng)用的基礎(chǔ)。每個 Widget 是一部分用戶界面上不可變的定義。和其他框架把 View、controller、 Layout 和其他資源分開定義不一樣,F(xiàn)lutter 具有一致的、唯一的對象模型: Widget。
一個 Widget 可以定義:
- 一個結(jié)構(gòu)性的元素(比如 按鈕或者菜單)
- 一個元素的風(fēng)格(比如 字體或者顏色)
- 指定布局屬性(比如 padding)
- 也可以包含一些業(yè)務(wù)邏輯
- 以及其他…
組合大于繼承(Composition > inheritance)
Widget 通常通過組合的方式來構(gòu)建復(fù)雜的 UI。例如,常用的 Container Widget 就是由幾個分別負(fù)責(zé) 布局、繪制、布局和計算大小的 Widget 組成。具體來說,Container 由
- LimitedBox,
- ConstrainedBox,
- Align,
- Padding,
- DecoratedBox,
- Transform
等widget 組成。如果要自定義 Container 來實(shí)現(xiàn)自定義效果,相比使用繼承而言,可以使用組合一些簡單的 Widget 實(shí)現(xiàn)自定義效果。
[圖片上傳失敗...(image-84ef2a-1543882561066)]
基礎(chǔ) Widget
主要文章: widget概述-布局模型
Flutter有一套豐富、強(qiáng)大的基礎(chǔ)widget,其中以下是很常用的:
Text:該 widget 可讓創(chuàng)建一個帶格式的文本。
Row、 Column: 這些具有彈性空間的布局類Widget可讓您在水平(Row)和垂直(Column)方向上創(chuàng)建靈活的布局。其設(shè)計是基于web開發(fā)中的Flexbox布局模型。
Stack: 取代線性布局 (譯者語:和Android中的LinearLayout相似),Stack允許子 widget 堆疊, 你可以使用 Positioned 來定位他們相對于Stack的上下左右四條邊的位置。Stacks是基于Web開發(fā)中的絕度定位(absolute positioning )布局模型設(shè)計的。
Container: Container 可讓您創(chuàng)建矩形視覺元素。container 可以裝飾為一個BoxDecoration, 如 background、一個邊框、或者一個陰影。 Container 也可以具有邊距(margins)、填充(padding)和應(yīng)用于其大小的約束(constraints)。另外, Container可以使用矩陣在三維空間中對其進(jìn)行變換。
flutter提供了友好的Widget 的查找地址,可以方便的找到自己組要的組件:
Stateful(有狀態(tài)) 和 Stateless(無狀態(tài)) widgets
什么是重點(diǎn)?
- 有些widgets是有狀態(tài)的, 有些是無狀態(tài)的
- 如果用戶與widget交互,widget會發(fā)生變化,那么它就是有狀態(tài)的.
- widget的狀態(tài)(state)是一些可以更改的值, 如一個slider滑動條的當(dāng)前值或checkbox是否被選中.
- widget的狀態(tài)保存在一個State對象中, 它和widget的布局顯示分離。
- 當(dāng)widget狀態(tài)改變時, State 對象調(diào)用setState(), 告訴框架去重繪widget.
stateless widget 沒有內(nèi)部狀態(tài). Icon、 IconButton, 和Text 都是無狀態(tài)widget, 他們都是StatelessWidget的子類。
stateful widget 是動態(tài)的. 用戶可以和其交互 (例如輸入一個表單、 或者移動一個slider滑塊),或者可以隨時間改變 (也許是數(shù)據(jù)改變導(dǎo)致的UI更新). Checkbox, Radio, Slider, InkWell, Form, and TextField 都是 stateful widgets, 他們都是 StatefulWidget的子類。
創(chuàng)建一個有狀態(tài)的widget
- 要創(chuàng)建一個自定義有狀態(tài)widget,需創(chuàng)建兩個類:StatefulWidget和State
- 狀態(tài)對象包含widget的狀態(tài)和build() 方法。
- 當(dāng)widget的狀態(tài)改變時,狀態(tài)對象調(diào)用setState(),告訴框架重繪widget
State的狀態(tài)管理
- 有多種方法可以管理狀態(tài).
- 選擇使用何種管理方法
- 如果不是很清楚時, 那就在父widget中管理狀態(tài)吧.
一下是常見狀態(tài)管理的幾種方法
如何決定使用哪種管理方法?以下原則可以幫助您決定:
- 如果狀態(tài)是用戶數(shù)據(jù),如復(fù)選框的選中狀態(tài)、滑塊的位置,則該狀態(tài)最好由父widget管理
- 如果所討論的狀態(tài)是有關(guān)界面外觀效果的,例如動畫,那么狀態(tài)最好由widget本身來管理.
- 如果有疑問,首選是在父widget中管理狀態(tài)
Flutter通信機(jī)制 使用平臺通道編寫平臺特定的代碼
平臺方法調(diào)用
平臺方法調(diào)用主要分四步:
- 定義好交互的協(xié)議名稱以及參數(shù)名稱(channelName 、methodName、parameter)
- 對應(yīng)平臺工程添加對應(yīng)的平臺調(diào)用代碼
- 實(shí)現(xiàn)MethodChannel.MethodCallHandler接口
- onMethodCall() 中根據(jù)參數(shù)做對應(yīng)處理
- 對應(yīng)平臺界面對應(yīng)位置注冊
- flutter中調(diào)用
- 創(chuàng)建MethodChannel(channelName)
- MethodChannel.invoke(methodName)
平臺事件調(diào)用
平臺事件調(diào)用和方法調(diào)用類似,步驟如下:
- 定義好交互的協(xié)議名稱以及參數(shù)名稱(eventlName)
- 對應(yīng)平臺工程添加對應(yīng)的平臺調(diào)用代碼
- 實(shí)現(xiàn)EventChannel.StreamHandler接口
- 在onListen中的EventChannel.EventSink eventSink可以用來發(fā)送事件的結(jié)果
- 對應(yīng)平臺界面對應(yīng)位置注冊
- flutter注冊事件
- 創(chuàng)建EventChannel(eventlName)
- 開始監(jiān)聽eventChannel.receiveBroadcastStream().listen(_onEvent, onError: _onError);
除了上面提到的MethodChannel,你還可以使用BasicMessageChannel,它支持使用自定義消息編解碼器進(jìn)行基本的異步消息傳遞。 此外,您可以使用專門的BinaryCodec,StringCodec和 JSONMessageCodec類,或創(chuàng)建自己的編解碼器。
Flutter 圖片
在Flutter中系統(tǒng)為我們提供了可以加載圖片的控件Image,Image 控件提供了如下幾種用于加載不同方式的圖片。
- new Image, 用于從ImageProvider獲取圖像。
- new Image.asset, 用于從AssetBundle獲取圖像。
- new Image.network, 用于從URL獲取圖像。
- new Image.file, 用于從文件中獲取圖像。
- new Image.memory, 用于從內(nèi)存中獲取圖像
在flutter中Image支持JPEG, PNG, GIF, Animated GIF, WebP, Animated WebP, BMP, 和 WBMP這幾種圖片格式。
今天我們主要介紹下兩種常用的方式,從asset目錄和從network獲取圖片
從asset目錄加載圖片
1. 新建存放圖片的目錄
想要使Image加載asset加載apk內(nèi)部的圖片,首先需要在項(xiàng)目中建立放置圖片的目錄(名稱隨意,不要中文就好)
我們在lib目錄的同級目錄建立images文件夾,并在里面放置了一個cover.jpg的圖片
2. 更細(xì)配置文件pubspec.yaml
在flutter節(jié)點(diǎn)下新增 文件聲明 ,如下所示
flutter:
assets:
- images/cover.jpg
3. 加載目錄中的圖片文件
new Image.asset("images/helloflutter.png")
從network獲取圖片
基本上和上面asset的參數(shù)相同,只不過從網(wǎng)絡(luò)獲取的方式可以傳入請求頭。
但是,從我網(wǎng)絡(luò)獲取圖片展示的調(diào)用方式就比從asset讀取顯示方便的多,只需要一行代碼就可以搞定
把Scaffold body的參數(shù)寫為:
new Image.network("http://pic1.win4000.com/wallpaper/2017-10-25/59f083092ed4f.jpg")
Flutter 網(wǎng)絡(luò)
注:本篇文檔官方使用的是用dart io中的HttpClient發(fā)起的請求,但HttpClient本身功能較弱,很多常用功能都不支持。我們建議您使用dio 來發(fā)起網(wǎng)絡(luò)請求,它是一個強(qiáng)大易用的dart http請求庫,支持Restful API、FormData、攔截器、請求取消、Cookie管理、文件上傳/下載……詳情請查看github dio
處理異步
注意,HTTP API 在返回值中使用了Dart Futures。 我們建議使用async/await語法來調(diào)用API。
網(wǎng)絡(luò)調(diào)用通常遵循如下步驟:
- 創(chuàng)建 client.
- 構(gòu)造 Uri.
- 發(fā)起請求, 等待請求,同時您也可以配置請求headers、 body。
- 關(guān)閉請求, 等待響應(yīng).
- 解碼響應(yīng)的內(nèi)容.
get() async {
var httpClient = new HttpClient();
var uri = new Uri.http(
'example.com', '/path1/path2', {'param1': '42', 'param2': 'foo'});
var request = await httpClient.getUrl(uri);
var response = await request.close();
var responseBody = await response.transform(UTF8.decoder).join();
}
Flutter給我們提供了第三發(fā)庫的支持,同樣的下面三個操作
- 打開項(xiàng)目的pubspec.yaml配置我文件在dependencies:節(jié)點(diǎn)下新增如下配置
http: ^0.11.3+16 - 點(diǎn)擊開發(fā)工具提示的packages get按鈕或者在命令行輸入flutter packages get來同步第三方插件
- 在自己的Dart文件中引入插件即可正常使用了
- import ‘package:http/http.dart’ as http
var url = "http://example.com/whatsit/create";
http.post(url, body: {"name": "doodle", "color": "blue"})
.then((response) {
print("Response status: ${response.statusCode}");
print("Response body: ${response.body}");
});
官網(wǎng)還退出了一個網(wǎng)絡(luò)第三方庫 github dio
Dio dio = new Dio();
_loadDataByDio() async {
try {
Response response = await dio.get("https://news-at.zhihu.com/api/4/news/latest");
if (response.statusCode == HttpStatus.ok) {
_result = response.data.toString();
} else {
_result = 'error code : ${response.statusCode}';
}
} catch (exception) {
print('exc:$exception');
_result = '網(wǎng)絡(luò)異常';
}
setState(() {});
}
Flutter路由
靜態(tài)路由
在Flutter中有著兩種路由跳轉(zhuǎn)的方式,一種是靜態(tài)路由,在創(chuàng)建時就已經(jīng)明確知道了要跳轉(zhuǎn)的頁面和值。另一種是動態(tài)路由,跳轉(zhuǎn)傳入的目標(biāo)地址和要傳入的值都可以是動態(tài)的。
OK,還是先來介紹下靜態(tài)路由
從我們開始學(xué)習(xí)Flutter到現(xiàn)在,相信大家看到最多的肯定是下面的代碼
void main() {
runApp(new MaterialApp(
home: new MyApp(),
routes: <String, WidgetBuilder>{
'/page2': (BuildContext context) => new Page2("requestString"),
},
));
}
routes: const {}
routes需要傳入類型的Map,第一個參數(shù)是目標(biāo)路由的名稱,第二個參數(shù)就是你要跳轉(zhuǎn)的頁面。
這種定義路由并使用的方式非常的簡單,但是大家肯定會發(fā)現(xiàn)一個問題,就是如果我需要傳遞給第二個頁面的數(shù)據(jù)不是已知的話我就無法使用這種方式,因?yàn)槲覀儫o法動態(tài)改變上面定義的值。
所以,我們就需要了解下Flutter中的動態(tài)路由了。
動態(tài)路由
在Navigator中還有一個方法是push()方法,需要傳入一個Route對象,在Flutter中我們可以使用PageRouteBuilder來構(gòu)建這個Route對象。
Navigator.of(context).push(new PageRouteBuilder(
pageBuilder: (BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation) {
return new Page2("some attrs you like ");
}))
這樣的話,我們就可以把用戶操作與交互的數(shù)據(jù)傳遞給下個頁面。
頁面出棧
在Flutter中我們可以使用Navigator.of(context).pop()進(jìn)行出棧操作,但是值得注意的時如果頁面上有Dialog、BottomSheet、popMenu類似的Widget使用pop()方法會優(yōu)先進(jìn)行這些Widget的關(guān)閉操作。
處理出棧頁面返回值
在前面我們介紹到Navigator.of(context).pop()可以使得頁面出棧,當(dāng)然這個pop方法也是可以傳值的,只用Navigator.of(context).pop(attrs)就可以傳入自己想要返回的值
Future future = Navigator.of(context).pushNamed("/pageB");
future.then((value) {
showDialog(
context: context,
child: new AlertDialog(
title: new Text(value),
));
}
小結(jié)
- 使用Navigator.of(context).pushName(“/“)可以進(jìn)行靜態(tài)路由的跳轉(zhuǎn)
- 使用push(Route)可以進(jìn)行態(tài)路由的跳轉(zhuǎn)
- 動態(tài)路由可以傳入未知數(shù)據(jù)
- 使用pop()可以進(jìn)行路由的出棧并且可以傳遞參數(shù)
- 可以使用Future接收上個頁面返回的值。
Flutter插件
官網(wǎng)提供了很多好用的插件,插件地址
- flutter_image
- 使用NetworkImageWithRetry 代替Image.network 加載網(wǎng)絡(luò)圖片可獲得重試能力。
- barcode_scan
- 一個可以掃描二維碼和條形碼的flutter插件。
- intl
- 該插件提供國際化和本地化設(shè)施,包括消息翻譯,復(fù)數(shù)和性別,日期/數(shù)字格式和解析以及雙向文本。
- location
- 這個插件 能夠處理Android和iOS設(shè)備的位置。它還提供位置更改時的回調(diào)。
等等。。。其他科自行搜索使用。
如果你想開發(fā)一個新的插件可以參考開發(fā)Packages和插件