本文承接上文,是Flutter For iOS 的第二篇文章,經過閱讀本文你將獲取以下信息:html
Dart擁有單線程執行模型,同時也支Isolate
(一種將Dart代碼執行在另外一個線程的方式)、事件循環和異步編程。除非你建立一個Isolate
,你的Dart代碼將一直在主UI線程中執行,並由事件循環驅動。Flutter的事件循環至關於iOS中的主循環,也就是說Looper
綁定在主線程上。ios
Dart的單線程模型並不意味着你必須將一切代碼做爲一個致使UI卡頓的阻塞塊來執行。相反,你可使用Dart提供的異步功能好比說:async/awiat
來執行異步任務。數據庫
好比說,你可使用asyn/await
執行網絡代碼和繁重的工做而避免UI卡頓。編程
一旦網絡請求結束,經過調用setState()
更新UI,觸發當前widget的子樹和更新數據。json
下面例子異步加載數據並展現在ListView
s上:api
參考下一節瞭解如何在後臺線程執行任務,與iOS有何不一樣。bash
因爲Flutter的單線程模型和事件循環,你不用擔憂線程管理或者開啓後臺線程。你能夠放心的使用async/await
方法執行I/O操做,好比訪問磁環或者請求網絡。另外一方面,如何你想執行復雜的計算而使CPU持續的處於繁忙狀態,你能夠將任務已到Isolate
而避免阻塞事件循環。微信
對於iOS操做,將方法聲明爲async
方法,使用await
等待耗時任務完成。網絡
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = json.decode(response.body);
});
}
複製代碼
這是對常的I/O操做如網絡請求,訪問數據庫的常規操做。app
可是,當你處理大量數據的時候這仍然可能會致使UI掛起。在Flutter中,使用Isolate
來使用CPU多核的優點來執行耗時任務或者計算密集型任務。
Isolates
是分離線程,它不和主線程共享任何堆內存,這也就意味着,你不能訪問主線程中的變臉,或者直接調用setState()
更新主線程。Isolates
正如其名,不能共享內存。
下面代碼展現了一個簡單的isolate
, 如何將數據返回到主線程並更新UI的。
上面代碼中,dataLoader()
是Isolate
,它在一個獨立的線程中執行。在這個isolate中你能夠執行CPU密集型任務如解析JSON,或者執行浮躁的數學計算任務,如加密或者信號處理。
你能夠執行完整代碼,以下:
在Flutter中使用流行的第三方庫http
package 來請求網絡是很是簡單的。它抽象了大量的本須要你本身實現的操做,使得發送請求很是簡單。
爲了使用http
這個框架,你須要在pubspec.yaml
中增長依賴。
dependencies:
...
http: ^0.11.3+16
複製代碼
爲了發起網絡請求,在async
方法http.get()
前添加await
。
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
[...]
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = json.decode(response.body);
});
}
}
複製代碼
在iOS中,當在後臺執行一個耗時任務的時候,你經過會使用UIProgressView
展現進度。
在Flutter中,使用ProgressIndicator
組件。經過給它傳遞一個布爾標識來控制它的展現,告訴Flutter去更新它的狀態在耗時任務執行以前和執行結束以後隱藏掉它。
在下面的例子中,build方法被分割爲三個不一樣方法。若是showLoadingDialog()
是true,那就渲染ProgressIndicator
不然使用網絡返回的數據渲染ListView
。
與iOS將圖片和資源做爲不一樣的類型來處理不一樣的是Flutter中只有一種assets。iOS中資源被放在Image.xcassert中文件中,而Flutter中放在assets文件中。與iOS同樣,assets是許多類型的文件,不只僅是圖片,好比說你能夠將json文件放到my-assets文件夾中。
my-assets/data.json
複製代碼
在pubspec.yaml
文件中聲明:
assets:
- my-assets/data.json
複製代碼
而後就能夠在代碼中使用AssetBunlde
訪問:
import 'dart:async' show Future;
import 'package:flutter/services.dart' show rootBundle;
Future<String> loadAsset() async {
return await rootBundle.loadString('my-assets/data.json');
}
複製代碼
對於圖片,Flutter和iOS的格式同樣,圖片能夠是1倍圖,2倍圖,3倍圖或者其餘任何倍數。這些所謂的 devicePixelRatio
表示的是物理像素到單個邏輯像素的比率。
Assets能夠被放到任何類型的文件夾中,Flutter中沒有事先預約義文件的結構。在pubSpec.yaml
文件中聲明assets,而後Flutter就能識別出來。
好比說:將my_icon.png
放置到Flutter項目中,你可能把存儲的文件夾叫做images。把相關係數的圖片放在不一樣的子文件家中,以下:a
images/my_icon.png // Base: 1.0x image
images/2.0x/my_icon.png // 2.0x image
images/3.0x/my_icon.png // 3.0x image
複製代碼
接下來在pubspec.yaml
中聲明圖片
assets:
- images/my_icon.png
複製代碼
你如今就可使用AssetImage返回圖片
return AssetImage("images/a_dot_burr.jpeg");
複製代碼
或者直接使用Image組件
@override
Widget build(BuildContext context) {
return Image.asset("images/my_image.png");
}
複製代碼
更多細節參考Adding Assets and Images in Flutter。
iOS中,咱們使用Localizable.strings
文件管理本地化字符串,而Flutter中沒有專門的模塊處理本地化字符串,因此最好的辦法就是將字符串統一放到一個類中,以靜態字段的形式存儲。以下:
class Strings {
static String welcomeMessage = "Welcome To Flutter";
}
複製代碼
訪問方式以下:
Text(Strings.welcomeMessage)
複製代碼
默認狀況下,Flutter只支持英文字符串,若是你想支持其餘語言,能夠經過引入flutter_localizations
庫。 同時你須要將Dart的intl
包以便支持 i10n 機制,好比日期/時間格式化。
dependencies:
# ...
flutter_localizations:
sdk: flutter
intl: "^0.15.6"
複製代碼
爲了使用flutter_localizations
,須要在App widget上指定 localizationsDelegates
和supportedLocales
屬性。
import 'package:flutter_localizations/flutter_localizations.dart';
MaterialApp(
localizationsDelegates: [
// Add app-specific localization delegate[s] here
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: [
const Locale('en', 'US'), // English
const Locale('he', 'IL'), // Hebrew
// ... other locales the app supports
],
// ...
)
複製代碼
代理中包含了實際的本地化值,supportedLocales
定義了要支持那些語言的本地化。上面的例子使用的是MaterialApp
, 它既有針對基本Widget的本地化值GlobalWidgetsLocalizations
,也有針對Material widget的MaterialWidgetsLocalizations
本地化。若是你的App使用的是WidgetApp
,那麼後者就不須要了。值得注意的是這兩個代理都包含默認值,但若是你想讓你的App本地化,你扔須要提供一個或者多個代理做爲你的App本地化副本。
當初始化完成的時候,WidgetsApp
或者MaterialApp
使用你指定的代理爲你建立了一個Localizations
widget。你可從Localizations
Widget中隨時訪問當前設備的本地化信息,或者使用window.locale
。
爲了訪問本地化資源,使用Localizations.of()
方法訪問有給定的delegate提供的特有的本地化類。使用intl_translation
取出翻譯副本到 arb 文件中。將它們引入App中,並用intl
來使用它們。
更多國際化和本地化的內容參考: internationalization guide,它包含了不使用intl
示例代碼。
須要注意的是:Flutter1.0 beta2 以前 fullter中定義的資源文件不能被原生訪問,同時原生定義的資源不能被flutter訪問,由於它們存儲在不能的文件目錄下。
在iOS中,咱們將依賴添加到Podfile
文件中,Flutter使用的是Dart語言構建的系統和Pub
包管理器操做依賴。這些工具將原生 Android 和 iOS 包裝應用程序的構建委派給相應的構建系統。
若是在你的Flutter項目中iOS目錄下包含Podfile,只須要使用它添加iOS原生的依賴。使用 pubspec.yaml
聲明Flutter 中的外部依賴。 Pub網站能夠找到一些比較好用的第三方依賴。
在iOS中,ViewController
表示用戶界面的一部分,一般表示一個屏幕或者部分屏幕。多個ViewController組合在一塊兒構造複雜的用戶界面,並幫助你規整應用的UI部分。在Flutter中,這項工做落在了Widget頭上,正如導航那一個章節提到的,屏幕由Widget所表示,因"一切都是Widget"。使用Navigator
在不一樣的路由間切換表示不一樣的屏幕或者頁面或者表示不一樣的狀態或者渲染相同的數據。
在iOS中,你能夠重寫ViewController
中的方法來捕獲視圖的生命週期,或者在AppDelegate
中註冊生命週期的回調。在Flutter中沒有這兩個概念,可是咱們能夠經過hookWidgetsBinding
並在didChangeAppLifecycleState()
方法中監聽生命週期事件。
可以監聽到的生命週期事件以下:
更多細節參考:AppLifecycleStatus documentation。
UITableView
和 UICollectionView
Flutter中使用ListView
實現iOS中的UITableView
和 UICollectionView
。實現代碼以下:
在iOS中,經過實現 tableView:didSelectRowAtIndexPath:
方法來相應cell的點擊事件,在Flutter中,使用所包含的widget自己提供的事件來處理相應。
在iOS中,咱們使用reloadData
來刷新表格視圖。
在Flutter中,若是更新setState()中的小部件列表,你會發現列表數據沒有發生變化。這是由於當調用setState()時,Flutter呈現引擎會查看widget樹以查看是否有任何更改。當它到達ListView時,它執行==檢查,並肯定兩個ListView是相同的。沒有任何改變,所以不須要更新。
在setState()方法內建立一個新List是更新ListView的一個簡單的方法。並將舊列表中的數據複製到新列表中。雖然這種方法很簡單,但不建議用於大型數據集,以下一個示例所示。
咱們推薦使用ListView.Builder
來構建列表,它比較高效。當你的列表包含大量數據的列表時,此方法很是有用。
與建立一個ListView
不一樣的是,建立ListView.builder
攜帶兩個參數:列表的初始長度和ItemBuilder
方法。
ItemBuilder
方法和iOS中的table或者collection的cellForItemAt
代理類似,同樣的攜帶一個位置,並返回該位置須要渲染的cell。
最後也是最重要的,onTap方法並無從新建立一個list,而是.add
了一個Widget。
@override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
Text('Row One'),
Text('Row Two'),
Text('Row Three'),
Text('Row Four'),
],
);
}
複製代碼
若是widget支持事件處理,如RaisedButton,能夠直接將相應方法傳遞給對應的屬性,如RaisedButton的onPressed。
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
print("click");
},
child: Text("Button"),
);
}
複製代碼
若是widget不支持事件處理,可使用GestureDetector
包裹一下,而後給onTap
屬性傳遞一個方法。
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Sample App'),
),
body: Center(
child: GestureDetector(
child: FlutterLogo(
size: 200,
),
onTap: () {
print('taped');
},
),
));
}
複製代碼
咱們可使用 GestureDetector
來實現以下事件的監聽:
onTapDown
— 按下手勢事件onTapUp
— 擡起事件onTap
— 點擊事件onTapCancel
— 取消點擊事件,onTapDown
發生,但onTap沒有發生。onDoubleTap
— 雙擊事件onLongPress
— 長按事件onVerticalDragStart
—開始垂直移動onVerticalDragUpdate
— 垂直移動進行中。onVerticalDragEnd
— 垂直移動結束。onHorizontalDragStart
— 開始水平移動。onHorizontalDragUpdate
— 水平移動進行中。onHorizontalDragEnd
— 水平移動結束。下面代碼展現了使用 GestureDetector
實現雙擊事件:
運行效果:
Flutter提供了一套完美符合Material Design的主題,它幫你處理了大多數須要你本身處理的樣式和主題。
爲了在你的App中充分發揮Material組件的優點,在頂層組件上聲明MaterialApp,做爲你的應用的入口。MaterialApp 是一個便利的組件,它包含了許多App一般須要的Materail Desigin風格的組件。它經過由給WidgetsApp增長MD功能實現的。
同時 Flutter 足夠地靈活和富有表現力來實現任何其餘的設計語言。在 iOS 上,你能夠用 Cupertino library 來製做遵照 Human Interface Guidelines 的界面。查看這些 widget 的集合,請參閱 Cupertino widgets gallery。
你也能夠在你的 App 中使用 WidgetApp,它提供了許多類似的功能,但不如 MaterialApp
那樣豐富。
對任何子組件定義顏色和樣式,能夠給 MaterialApp
widget 傳遞一個 ThemeData
對象。舉個例子,在下面的代碼中,primary swatch 被設置爲藍色,而且文字的選中顏色是紅色:
class SampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
textSelectionColor: Colors.red
),
home: SampleAppPage(),
);
}
}
複製代碼
在 iOS 中,你在項目中引入任意的 ttf
文件,並在 info.plist
中設置引用。在 Flutter 中,在文件夾中放置字體文件,並在 pubspec.yaml
中引用它,就像添加圖片那樣。
fonts:
- family: MyCustomFont
fonts:
- asset: fonts/MyCustomFont.ttf
- style: italic
複製代碼
而後在你的 Text
widget 中指定字體:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: Center(
child: Text(
'This is a custom font text',
style: TextStyle(fontFamily: 'MyCustomFont'),
),
),
);
}
複製代碼
除了字體之外,你也能夠給 Text widget 的樣式元素設置自定義值。Text
widget 接受一個 TextStyle
對象,你能夠指定許多參數,以下:
color
decoration
decorationColor
decorationStyle
fontFamily
fontSize
fontStyle
fontWeight
hashCode
height
inherit
letterSpacing
textBaseline
wordSpacing
在iOS中,咱們一般在用戶提交的時候獲取組件上的內容,對於具備使用獨立狀態的不可變組件的Flutter來說,你可能會好奇如何獲取用戶輸入內容。
對於表單操做而言,與其餘功能同樣也是經過特定的Widget實現的。經過使用 TextField
或者TextFormField
能夠經過 TextEditingController
取回輸入內容。
示例代碼以下:
運行效果
更多信息參考: Flutter Cookbook 的 Retrieve the value of a text field
經過給decoration屬性傳遞一個InputDecoration
對象來給TextField實現佔位符的功能。
body: Center(
child: TextField(
decoration: InputDecoration(hintText: "This is a hint"),
),
)
複製代碼
與上面代碼同樣,只不過是再添加一個errorText
字段,經過state控制錯誤信息的提示。
示例代碼以下:
運行效果:
Flutter不是在直接在平臺下運行代碼的,相反,由Dart語言構建的FlutterApp在設備本機運行,"迴避"平臺提供的SDK。好比說:在Dart中發送一個網絡請求,它是直接在Dart上下文中執行的,而不適用咱們在寫原生App的時候所使用的Android或者iOSAPI。咱們的FlutterApp仍然被原生app的ViewController當作一個View所持有,但咱們不用直接訪問ViewController或者原生框架。
這並不意味着Flutter應用不能與原生API或者其餘你寫的原生代碼交互。Flutter提供了 platform channels,它能夠與持有你Flutter視圖的VIewController通訊或者交換數據。platform channels 本質上是一個異步通訊機制,橋接了Dart代碼和其宿主ViewController,iOS框架。好比說。你能夠用platform channels執行一個原生的函數,或者是從設備的傳感器中獲取數據。
除了直接使用platform channels以外,你還可使用一系列預先製做好的 plugins。例如,你能夠直接使用插件來訪問相機膠捲或是設備的攝像頭,而沒必要編寫你本身的集成層代碼。你能夠在 Pub 上找到插件,這是一個 Dart 和 Flutter 的開源包倉庫。其中一些包可能會支持集成 iOS 或 Android,或二者都可。
若是你在 Pub 上找不到符合你需求的插件,你能夠本身編寫 ,而且發佈在 Pub 上。
使用 geolocator
使用 image_picker
大多數 Firebase 特性被 first party plugins 包含了。這些第一方插件由 Flutter 團隊維護:
若是有一些 Flutter 和社區插件遺漏的平臺相關的特性,能夠根據 developing packages and plugins 頁面構建本身的插件。 Flutter 的插件結構,簡要來講,就像 Android 中的 Event bus。你發送一個消息,並讓接受者處理並反饋結果給你。在這種狀況下,接受者就是在 Android 或 iOS 上的原生代碼。
在iOS中,咱們可使用UserDefaults
來存儲鍵值對集合,在Flutter中,可使用 Shared Preferences plugin插件來顯示相似的功能。 這個插件包裝了UserDefaults
和Android 上的 SharedPreferences
。
可使用 SQFlite 插件實現iOS中CoreData相關的功能。
在iOS,你須要在開發者網站上註冊app以便獲取推送權限。在Flutter中使用firebase_messaging
插件能夠實現推送。 更多關於使用Firebase Cloud Messaging API
的文檔請參考: firebase_messaging
本文主要參考Flutter官方文檔,Flutter中文網。 因爲排版緣由,文中我使用了圖片的形式展現代碼,若是你須要源碼,能夠關注個人公衆號,回覆關鍵字"flutter"獲取相關代碼。
本文首發自微信公衆號:RiverLi