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