iOS開發者學習Flutter

Flutter for iOS 開發者

本文檔適用那些但願將現有 iOS 經驗應用於 Flutter 的開發者。若是你擁有 iOS 開發基礎,那麼你可使用這篇文檔開始學習 Flutter 的開發。javascript

開發 Flutter 時,你的 iOS 經驗和技能將會大有裨益,由於 Flutter 依賴於移動操做系統的衆多功能和配置。Flutter 是用於爲移動設備構建用戶界面的全新方式,但它也有一個插件系統用於和 iOS(及 Android)進行非 UI 任務的通訊。若是你是 iOS 開發專家,則你沒必要將 Flutter 完全從新學習一遍。html

你能夠將此文檔做爲 cookbook,經過跳轉並查找與你的需求最相關的問題。java

Views

UIView 至關於 Flutter 中的什麼?

在 iOS 中,構建 UI 的過程當中將大量使用 view 對象。這些對象都是 UIView 的實例。它們能夠用做容器來承載其餘的 UIView,最終構成你的界面佈局。ios

在 Flutter 中,你能夠粗略地認爲 Widget 至關於 UIView 。Widget 和 iOS 中的控件並不徹底等價,但當你試圖去理解 Flutter 是如何工做的時候,你能夠認爲它們是「聲明和構建 UI 的方法」。算法

然而,Widget 和 UIView 仍是有些區別的。首先,widgets 擁有不一樣的生存時間:它們一直存在且保持不變,直到當它們須要被改變。當 widgets 和它們的狀態被改變時,Flutter 會構建一顆新的 widgets 樹。做爲對比,iOS 中的 views 在改變時並不會被從新建立。可是與其說 views 是可變的實例,不如說它們被繪製了一次,而且直到使用 setNeedsDisplay() 以後纔會被從新繪製。數據庫

此外,不像 UIView,因爲不可變性,Flutter 的 widgets 很是輕量。這是由於它們自己並非什麼控件,也不會被直接繪製出什麼,而只是 UI 的描述。編程

Flutter 包含了 Material 組件庫。這些 widgets 遵循了 Material 設計規範。MD 是一個靈活的設計系統,而且爲包括 iOS 在內的全部系統進行了優化json

可是用 Flutter 實現任何的設計語言都很是的靈活和富有表現力。在 iOS 平臺,你可使用 Cupertino widgets 來構建遵循了 Apple’s iOS design language 的界面。canvas

我怎麼來更新 Widgets?

在 iOS 上更新 views,只須要直接改變它們就能夠了。在 Flutter 中,widgets 是不可變的,並且不能被直接更新。你須要去操縱 widget 的 state。安全

這也正是有狀態的和無狀態的 widget 這一律唸的來源。一個 StatelessWidget 正如它聽起來同樣,是一個沒有附加狀態的 widget。

StatelessWidget 在你構建初始化後再也不進行改變的界面時很是有用。

舉個例子,你可能會用一個 UIImageView 來展現你的 logo image 。若是這個 logo 在運行時不會改變,那麼你就能夠在 Flutter 中使用 StatelessWidget 。

若是你但願在發起 HTTP 請求時,依託接收到的數據動態的改變 UI,請使用 StatefulWidget。當 HTTP 請求結束後,通知 Flutter 框架 widget 的 State 更新了,好讓系統來更新 UI。

有狀態和無狀態的 widget 之間一個很是重要的區別是,StatefulWidget 擁有一個 State 對象來存儲它的狀態數據,並在 widget 樹重建時攜帶着它,所以狀態不會丟失。

若是你有疑惑,請記住如下規則:若是一個 widget 在它的 build 方法以外改變(例如,在運行時因爲用戶的操做而改變),它就是有狀態的。若是一個 widget 在一次 build 以後永遠不變,那它就是無狀態的。可是,即使一個 widget 是有狀態的,包含它的父親 widget 也能夠是無狀態的,只要父 widget 自己不響應這些變化。

下面的例子展現瞭如何使用一個 StatelessWidget 。一個常見的 StatelessWidget 是 Textwidget。若是你查看 Text 的實現,你會發現它是 StatelessWidget 的子類。

Text( 'I like Flutter!', style: TextStyle(fontWeight: FontWeight.bold), );

閱讀上面的代碼,你可能會注意到 Text widget 並不顯示地攜帶任何狀態。它經過傳入給它的構造器的數據來渲染,除此以外再無其餘。

可是,若是你但願 I like Flutter 在點擊 FloatingActionButton 時動態的改變呢?

爲了實現這個,用 StatefulWidget 包裹 Text widget,並在用戶點擊按鈕時更新它。

舉個例子:

class SampleApp extends StatelessWidget { // This widget is the root of your application. 

我怎麼對 widget 佈局?個人 Storyboard 在哪?

在 iOS 中,你可能會用 Storyboard 文件來組織 views,並對它們設置約束,或者,你可能在 view controller 中使用代碼來設置約束。在 Flutter 中,你經過編寫一個 widget 樹來聲明你的佈局。

下面這個例子展現瞭如何展現一個帶有 padding 的簡單 widget:

你能夠給任何的 widget 添加 padding,這很像 iOS 中約束的功能。

你能夠在 widget catalog 中查看 Flutter 提供的佈局。

我怎麼在個人約束中添加或移除組件?

在 iOS 中,你在父 view 中調用 addSubview() 或在子 view 中調用 removeFromSuperview() 來動態地添加或移除子 views。在 Flutter 中,因爲 widget 不可變,因此沒有和 addSubview() 直接等價的東西。做爲替代,你能夠向 parent 傳入一個返回 widget 的函數,並用一個布爾值來控制子 widget 的建立。

下面這個例子展現了在點擊 FloatingActionButton 時如何動態地切換兩個 widgets:

class SampleApp extends StatelessWidget { // This widget is the root of your application. 

我怎麼對 widget 作動畫?

在 iOS 中,你經過調用 animate(withDuration:animations:) 方法來給一個 view 建立動畫。在 Flutter 中,使用動畫庫來包裹 widgets,而不是建立一個動畫 widget。

在 Flutter 中,使用 AnimationController 。這是一個能夠暫停、尋找、中止、反轉動畫的 Animation<double> 類型。它須要一個 Ticker 當 vsync 發生時來發送信號,而且在每幀運行時建立一個介於 0 和 1 之間的線性插值(interpolation)。你能夠建立一個或多個的 Animation 並附加給一個 controller。

例如,你可能會用 CurvedAnimation 來實現一個 interpolated 曲線。在這個場景中,controller 是動畫過程的「主人」,而 CurvedAnimation 計算曲線,並替代 controller 默認的線性模式。

當構建 widget 樹時,你會把 Animation 指定給一個 widget 的動畫屬性,好比 FadeTransition 的 opacity,並告訴控制器開始動畫。

下面這個例子展現了在點擊 FloatingActionButton 以後,如何使用 FadeTransition 來讓 widget 淡出到 logo 圖標:

class SampleApp extends StatelessWidget { // This widget is the root of your application. 

更多信息,請參閱 Animation & Motion widgets, Animations tutorial 以及 Animations overview

我該怎麼繪圖?

在 iOS 上,你經過 CoreGraphics 來在屏幕上繪製線條和形狀。Flutter 有一套基於 Canvas 類的不一樣的 API,還有 CustomPaint 和 CustomPainter 這兩個類來幫助你繪圖。後者實現你在 canvas 上的繪圖算法。

想要學習如何實現一個筆跡畫筆,請參考 Collin 在 StackOverflow 上的回答。

class SignaturePainter extends CustomPainter { SignaturePainter(this.points); final List<Offset> points; void paint(Canvas canvas, Size size) { var paint = Paint() ..color = Colors.black ..strokeCap = StrokeCap.round ..strokeWidth = 5.0; for (int i = 0; i < points.length - 1; i++) { if (points[i] != null && points[i + 1] != null) canvas.drawLine(points[i], points[i + 1], paint); } } bool shouldRepaint(SignaturePainter other) => other.points != points; } class Signature extends StatefulWidget { SignatureState createState() => SignatureState(); } class SignatureState extends State<Signature> { List<Offset> _points = <Offset>[]; Widget build(BuildContext context) { return GestureDetector( onPanUpdate: (DragUpdateDetails details) { setState(() { RenderBox referenceBox = context.findRenderObject(); Offset localPosition = referenceBox.globalToLocal(details.globalPosition); _points = List.from(_points)..add(localPosition); }); }, onPanEnd: (DragEndDetails details) => _points.add(null), child: CustomPaint(painter: SignaturePainter(_points), size: Size.infinite), ); } } 

Widget 的透明度在哪裏?

在 iOS 中,什麼東西都會有一個 .opacity 或是 .alpha 的屬性。在 Flutter 中,你須要給 widget 包裹一個 Opacity widget 來作到這一點。

我怎麼建立自定義的 widgets?

在 iOS 中,你編寫 UIView 的子類,或使用已經存在的 view 來重載並實現方法,以達到特定的功能。在 Flutter 中,你會組合(composing)多個小的 widgets 來構建一個自定義的 widget(而不是擴展它)。

舉個例子,若是你要構建一個 CustomButton ,並在構造器中傳入它的 label?那就組合 RaisedButton 和 label,而不是擴展 RaisedButton

class CustomButton extends StatelessWidget { final String label; CustomButton(this.label); 

而後就像你使用其餘任何 Flutter 的 widget 同樣,使用你的 CustomButton:

導航

我怎麼在不一樣頁面之間跳轉?

在 iOS 中,你可使用管理了 view controller 棧的 UINavigationController 來在不一樣的 view controller 之間跳轉。

Flutter 也有相似的實現,使用了 Navigator 和 Routes。一個路由是 App 中「屏幕」或「頁面」的抽象,而一個 Navigator 是管理多個路由的 widget 。你能夠粗略地把一個路由對應到一個 UIViewController。Navigator 的工做原理和 iOS 中 UINavigationController 很是類似,當你想跳轉到新頁面或者重新頁面返回時,它能夠 push() 和 pop() 路由。

在頁面之間跳轉,你有一對選擇:

  • 具體指定一個由路由名構成的 Map。(MaterialApp)
  • 直接跳轉到一個路由。(WidgetApp)

下面是構建一個 Map 的例子:

void main() { runApp(MaterialApp( home: MyAppHome(), // becomes the route named '/' routes: <String, WidgetBuilder> { '/a': (BuildContext context) => MyPage(title: 'page A'), '/b': (BuildContext context) => MyPage(title: 'page B'), '/c': (BuildContext context) => MyPage(title: 'page C'), }, )); } 

經過把路由的名字 push 給一個 Navigator 來跳轉:

Navigator.of(context).pushNamed('/b');

Navigator 類不只用來處理 Flutter 中的路由,還被用來獲取你剛 push 到棧中的路由返回的結果。經過 await等待路由返回的結果來達到這點。

舉個例子,要跳轉到「位置」路由來讓用戶選擇一個地點,你可能要這麼作:

Map coordinates = await Navigator.of(context).pushNamed('/location');

以後,在 location 路由中,一旦用戶選擇了地點,攜帶結果一塊兒 pop() 出棧:

Navigator.of(context).pop({"lat":43.821757,"long":-79.226392});

我怎麼跳轉到其餘 App?

在 iOS 中,要跳轉到其餘 App,你須要一個特定的 URL Scheme。對系統級別的 App 來講,這個 scheme 取決於 App。爲了在 Flutter 中實現這個功能,你能夠建立一個原平生臺的整合層,或者使用現有的 plugin,例如 url_launcher

線程和異步

我怎麼編寫異步的代碼?

Dart 是單線程執行模型,可是它支持 Isolate(一種讓 Dart 代碼運行在其餘線程的方式)、事件循環和異步編程。除非你本身建立一個 Isolate ,不然你的 Dart 代碼永遠運行在 UI 線程,並由 event loop 驅動。Flutter 的 event loop 和 iOS 中的 main loop 類似——Looper 是附加在主線程上的。

Dart 的單線程模型並不意味着你寫的代碼必定是阻塞操做,從而卡住 UI。相反,使用 Dart 語言提供的異步工具,例如 async / await ,來實現異步操做。

舉個例子,你可使用 async / await 來讓 Dart 幫你作一些繁重的工做,編寫網絡請求代碼而不會掛起 UI:

loadData() async { String dataURL = "https://jsonplaceholder.typicode.com/posts"; http.Response response = await http.get(dataURL); setState(() { widgets = json.decode(response.body); }); } 

一旦 await 到網絡請求完成,經過調用 setState() 來更新 UI,這會觸發 widget 子樹的重建,並更新相關數據。

下面的例子展現了異步加載數據,並用 ListView 展現出來:

import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; void main() { runApp(SampleApp()); } class SampleApp extends StatelessWidget { 

更多關於在後臺工做的信息,以及 Flutter 和 iOS 的區別,請參考下一章節。

你是怎麼把工做放到後臺線程的?

因爲 Flutter 是單線程而且跑着一個 event loop 的(就像 Node.js 那樣),你沒必要爲線程管理或是開啓後臺線程而操心。若是你正在作 I/O 操做,如訪問磁盤或網絡請求,安全地使用 async / await 就完事了。若是,在另外的狀況下,你須要作讓 CPU 執行繁忙的計算密集型任務,你須要使用 Isolate來避免阻塞 event loop。

對於 I/O 操做,經過關鍵字 async,把方法聲明爲異步方法,而後經過await關鍵字等待該異步方法執行完成(譯者語:這和javascript中是相同的):

loadData() async { String dataURL = "https://jsonplaceholder.typicode.com/posts"; http.Response response = await http.get(dataURL); setState(() { widgets = json.decode(response.body); }); } 

這就是對諸如網絡請求或數據庫訪問等 I/O 操做的典型作法。

然而,有時候你須要處理大量的數據,這會致使你的 UI 掛起。在 Flutter 中,使用 Isolate 來發揮多核心 CPU 的優點來處理那些長期運行或是計算密集型的任務。

Isolates 是分離的運行線程,而且不和主線程的內存堆共享內存。這意味着你不能訪問主線程中的變量,或者使用 setState() 來更新 UI。正如它們的名字同樣,Isolates 不能共享內存。

下面的例子展現了一個簡單的 isolate,是如何把數據返回給主線程來更新 UI 的:

loadData() async { ReceivePort receivePort = ReceivePort(); await Isolate.spawn(dataLoader, receivePort.sendPort); // The 'echo' isolate sends its SendPort as the first message SendPort sendPort = await receivePort.first; List msg = await sendReceive(sendPort, "https://jsonplaceholder.typicode.com/posts"); setState(() { widgets = msg; }); } // The entry point for the isolate static dataLoader(SendPort sendPort) async { // Open the ReceivePort for incoming messages. ReceivePort port = ReceivePort(); // Notify any other isolates what port this isolate listens to. sendPort.send(port.sendPort); await for (var msg in port) { String data = msg[0]; SendPort replyTo = msg[1]; String dataURL = data; http.Response response = await http.get(dataURL); // Lots of JSON to parse replyTo.send(json.decode(response.body)); } } Future sendReceive(SendPort port, msg) { ReceivePort response = ReceivePort(); port.send([msg, response.sendPort]); return response.first; } 

這裏,dataLoader() 是一個運行於本身獨立執行線程上的 Isolate。在 isolate 裏,你能夠執行 CPU 密集型任務(例如解析一個龐大的 json),或是計算密集型的數學操做,如加密或信號處理等。

你能夠運行下面的完整例子:

import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'dart:async'; import 'dart:isolate'; void main() { runApp(SampleApp()); } class SampleApp extends StatelessWidget { 

我怎麼發起網絡請求?

在 Flutter 中,使用流行的 http package 作網絡請求很是簡單。它把你可能須要本身作的網絡請求操做抽象了出來,讓發起請求變得簡單。

要使用 http 包,在 pubspec.yaml 中把它添加爲依賴:

dependencies:
  ...
  http: ^0.11.3+16

發起網絡請求,在 http.get() 這個 async 方法中使用 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 widget。經過一個布爾 flag 來控制是否展現進度。在任務開始時,告訴 Flutter 更新狀態,並在結束後隱去。

在下面的例子中,build 函數被拆分紅三個函數。若是 showLoadingDialog() 是 true (當 widgets.length == 0 時),則渲染 ProgressIndicator。不然,當數據從網絡請求中返回時,渲染 ListView 。

import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; void main() { runApp(SampleApp()); } class SampleApp extends StatelessWidget { 

工程結構、本地化、依賴和資源

我怎麼在 Flutter 中引入 image assets?多分辨率怎麼辦?

iOS 把 images 和 assets 做爲不一樣的東西,而 Flutter 中只有 assets。被放到 iOS 中 Images.xcasset 文件夾下的資源在 Flutter 中被放到了 assets 文件夾中。assets 能夠是任意類型的文件,而不只僅是圖片。例如,你能夠把 json 文件放置到 my-assets 文件夾中。

my-assets/data.json

在 pubspec.yaml 文件中聲明 assets:

assets:
 - my-assets/data.json

而後在代碼中使用 AssetBundle 來訪問它:

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 同樣,遵循了一個簡單的基於像素密度的格式。Image assets 多是 1.0x2.0x 3.0x 或是其餘的任何倍數。這些所謂的 devicePixelRatio 傳達了物理像素到單個邏輯像素的比率。

Assets 能夠被放置到任何屬性文件夾中——Flutter 並無預先定義的文件結構。在 pubspec.yaml文件中聲明 assets (和位置),而後 Flutter 會把他們識別出來。

舉個例子,要把一個叫 my_icon.png 的圖片放到 Flutter 工程中,你可能想要把存儲它的文件夾叫作 images。把基礎圖片(1.0x)放置到 images 文件夾中,並把其餘變體放置在子文件夾中,並接上合適的比例係數:

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.jpeg

你能夠用 AssetImage 來訪問這些圖片:

return AssetImage("images/a_dot_burr.jpeg");

或者在 Image widget 中直接使用:

更多細節,參見 Adding Assets and Images in Flutter

我在哪裏放置字符串?我怎麼作本地化?

不像 iOS 擁有一個 Localizable.strings 文件,Flutter 目前並無一個用於處理字符串的系統。目前,最佳實踐是把你的文本拷貝到靜態區,並在這裏訪問。例如:

class Strings { static String welcomeMessage = "Welcome To Flutter"; } 

而且這樣訪問你的字符串:

Text(Strings.welcomeMessage) 

默認狀況下,Flutter 只支持美式英語字符串。若是你要支持其餘語言,請引入 flutter_localizations 包。你可能也要引入  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 定義了 App 支持哪些地區。上面的例子使用了一個 MaterialApp ,因此它既有 GlobalWidgetsLocalizations 用於基礎 widgets,也有 MaterialWidgetsLocalizations 用於 Material wigets 的本地化。若是你使用 WidgetsApp ,則無需包括後者。注意,這兩個代理雖然包括了「默認」值,但若是你想讓你的 App 本地化,你仍須要提供一或多個代理做爲你的 App 本地化副本。

當初始化時,WidgetsApp 或 MaterialApp 會使用你指定的代理爲你建立一個  Localizationswidget。Localizations widget 能夠隨時從當前上下文中訪問設備的地點,或者使用 Window.locale

要訪問本地化文件,使用 Localizations.of() 方法來訪問提供代理的特定本地化類。如需翻譯,使用  intl_translation 包來取出翻譯副本到 arb 文件中。把它們引入 App 中,並用 intl 來使用它們。

更多 Flutter 中國際化和本地化的細節,請訪問 internationalization guide ,那裏有不使用 intl 包的示例代碼。

注意,在 Flutter 1.0 beta 2 以前,在 Flutter 中定義的 assets 不能在原生一側被訪問。原生定義的資源在 Flutter 中也不可用,由於它們在獨立的文件夾中。

Cocoapods 至關於什麼?我該如何添加依賴?

在 iOS 中,你把依賴添加到 Podfile 中。Flutter 使用 Dart 構建系統和 Pub 包管理器來處理依賴。這些工具將本機 Android 和 iOS 包裝應用程序的構建委派給相應的構建系統。

若是你的 Flutter 工程中的 iOS 文件夾中擁有 Podfile,請僅在你爲每一個平臺集成時使用它。整體來講,使用 pubspec.yaml 來在 Flutter 中聲明外部依賴。一個能夠找到優秀 Flutter 包的地方是 Pub

ViewControllers

ViewController 至關於 Flutter 中的什麼?

在 iOS 中,一個 ViewController 表明了用戶界面的一部分,最經常使用於一個屏幕,或是其中一部分。它們被組合在一塊兒用於構建複雜的用戶界面,並幫助你拆分 App 的 UI。在 Flutter 中,這一任務回落到了 widgets 中。就像在界面導航部分提到的同樣,一個屏幕也是被 widgets 來表示的,由於「萬物皆 widget!」。使用 Navigator 在 Route 之間跳轉,或者渲染相同數據的不一樣狀態。

我該怎麼監聽 iOS 中的生命週期事件?

在 iOS 中,你能夠重寫 ViewController 中的方法來補貨它的視圖的生命週期,或者在 AppDelegate 中註冊生命週期的回調函數。在 Flutter 中沒有這兩個概念,但你能夠經過 hook WidgetsBinding 觀察者來監聽生命週期事件,並監聽 didChangeAppLifecycleState() 的變化事件。

可觀察的生命週期事件有:

  • inactive - 應用處於不活躍的狀態,而且不會接受用戶的輸入。這個事件僅工做在 iOS 平臺,在 Android 上沒有等價的事件。
  • paused - 應用暫時對用戶不可見,雖然不接受用戶輸入,可是是在後臺運行的。
  • resumed - 應用可見,也響應用戶的輸入。
  • suspending - 應用暫時被掛起,在 iOS 上沒有這一事件。

更多關於這些狀態的細節和含義,請參見  AppLifecycleStatus documentation 。

佈局

UITableView 和 UICollectionView 至關於 Flutter 中的什麼?

在 iOS 中,你可能用 UITableView 或 UICollectionView 來展現一個列表。在 Flutter 中,你能夠用 ListView 來達到類似的實現。在 iOS 中,你經過代理方法來肯定行數,每個 index path 的單元格,以及單元格的尺寸。

因爲 Flutter 中 widget 的不可變特性,你須要向 ListView 傳遞一個 widget 列表,Flutter 會確保滾動是快速且流暢的。

import 'package:flutter/material.dart'; void main() { runApp(SampleApp()); } class SampleApp extends StatelessWidget { // This widget is the root of your application. 

我怎麼知道列表的哪一個元素被點擊了?

iOS 中,你經過 tableView:didSelectRowAtIndexPath: 代理方法來實現。在 Flutter 中,使用傳遞進來的 widget 的 touch handle:

import 'package:flutter/material.dart'; void main() { runApp(SampleApp()); } class SampleApp extends StatelessWidget { // This widget is the root of your application. 

我怎麼動態地更新 ListView?

在 iOS 中,你改變列表的數據,並經過 reloadData() 方法來通知 table 或是 collection view。

在 Flutter 中,若是你想經過 setState() 方法來更新 widget 列表,你會很快發現你的數據展現並無變化。這是由於當 setState() 被調用時,Flutter 渲染引擎會去檢查 widget 樹來查看是否有什麼地方被改變了。當它獲得你的 ListView 時,它會使用一個 == 判斷,而且發現兩個 ListView 是相同的。沒有什麼東西是變了的,所以更新不是必須的。

一個更新 ListView 的簡單方法是,在 setState() 中建立一個新的 list,並把舊 list 的數據拷貝給新的 list。雖然這樣很簡單,但當數據集很大時,並不推薦這樣作:

import 'package:flutter/material.dart'; void main() { runApp(SampleApp()); } class SampleApp extends StatelessWidget { // This widget is the root of your application. 

一個推薦的、高效的且有效的作法是,使用 ListView.Builder 來構建列表。這個方法在你想要構建動態列表,或是列表擁有大量數據時會很是好用。

import 'package:flutter/material.dart'; void main() { runApp(SampleApp()); } class SampleApp extends StatelessWidget { // This widget is the root of your application. 

與建立一個 「ListView」 不一樣,建立一個 ListView.builder 接受兩個主要參數:列表的初始長度,和一個 ItemBuilder 方法。

ItemBuilder 方法和 cellForItemAt 代理方法很是相似,它接受一個位置,而且返回在這個位置上你但願渲染的 cell。

最後,也是最重要的,注意 onTap() 函數裏並無從新建立一個 list,而是 .add 了一個 widget。

ScrollView 至關於 Flutter 裏的什麼?

在 iOS 中,你給 view 包裹上 ScrollView 來容許用戶在須要時滾動你的內容。

在 Flutter 中,最簡單的方法是使用 ListView widget。它表現得既和 iOS 中的 ScrollView 一致,也能和 TableView 一致,由於你能夠給它的 widget 作垂直排布:

更多關於在 Flutter 總如何排布 widget 的文檔,請參閱 layout tutorial

手勢檢測及觸摸事件處理

我怎麼給 Flutter 的 widget 添加一個點擊監聽者?

在 iOS 中,你給一個 view 添加 GestureRecognizer 來處理點擊事件。在 Flutter 中,有兩種方法來添加點擊監聽者:

  1. 若是 widget 自己支持事件監測,直接傳遞給它一個函數,並在這個函數裏實現響應方法。例如,RaisedButton widget 擁有一個 RaisedButton 參數:

  2. 若是 widget 自己不支持事件監測,則在外面包裹一個 GestureDetector,並給它的 onTap 屬性傳遞一個函數:

    class SampleApp extends StatelessWidget { 

我怎麼處理 widget 上的其餘手勢?

使用 GestureDetector 你能夠監聽更廣闊範圍內的手勢,好比:

  • Tapping
    • onTapDown — 在特定位置輕觸手勢接觸了屏幕。
    • onTapUp — 在特定位置產生了一個輕觸手勢,並中止接觸屏幕。
    • onTap — 產生了一個輕觸手勢。
    • onTapCancel — 觸發了 onTapDown 但沒能觸發 tap。
  • Double tapping
    • onDoubleTap — 用戶在同一個位置快速點擊了兩下屏幕。
  • Long pressing
    • onLongPress — 用戶在同一個位置長時間接觸屏幕。
  • Vertical dragging
    • onVerticalDragStart — 接觸了屏幕,而且可能會垂直移動。
    • onVerticalDragUpdate — 接觸了屏幕,並繼續在垂直方向移動。
    • onVerticalDragEnd — 以前接觸了屏幕並垂直移動,並在中止接觸屏幕前以某個垂直的速度移動。
  • Horizontal dragging
    • onHorizontalDragStart — 接觸了屏幕,而且可能會水平移動。
    • onHorizontalDragUpdate — 接觸了屏幕,並繼續在水平方向移動。
    • onHorizontalDragEnd — 以前接觸屏幕並水平移動的觸摸點與屏幕分離。

下面這個例子展現了一個 GestureDetector 是如何在雙擊時旋轉 Flutter 的 logo 的:

AnimationController controller; CurvedAnimation curve; 

主題和文字

我怎麼給 App 設置主題?

Flutter 實現了一套漂亮的 MD 組件,而且開箱可用。它接管了一大堆你須要的樣式和主題。

爲了充分發揮你的 App 中 MD 組件的優點,聲明一個頂級 widget,MaterialApp,用做你的 App 入口。MaterialApp 是一個便利組件,包含了許多 App 一般須要的 MD 風格組件。它經過一個 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 { 

我怎麼給 Text widget 設置自定義字體?

在 iOS 中,你在項目中引入任意的 ttf 文件,並在 info.plist 中設置引用。在 Flutter 中,在文件夾中放置字體文件,並在 pubspec.yaml 中引用它,就像添加圖片那樣。

fonts:
   - family: MyCustomFont
     fonts:
       - asset: fonts/MyCustomFont.ttf
       - style: italic

而後在你的 Text widget 中指定字體:

我怎麼給個人 Text widget 設置樣式?

除了字體之外,你也能夠給 Text widget 的樣式元素設置自定義值。Text widget 接受一個  TextStyle 對象,你能夠指定許多參數,好比:

  • color
  • decoration
  • decorationColor
  • decorationStyle
  • fontFamily
  • fontSize
  • fontStyle
  • fontWeight
  • hashCode
  • height
  • inherit
  • letterSpacing
  • textBaseline
  • wordSpacing

表單輸入

Flutter 中表單怎麼工做?我怎麼拿到用戶的輸入?

咱們已經提到 Flutter 使用不可變的 widget,而且狀態是分離的,你可能會好奇在這種情境下怎麼處理用戶的輸入。在 iOS 中,你常常在須要提交數據時查詢組件當前的狀態或動做,但這在 Flutter 中是怎麼工做的呢?

在表單處理的實踐中,就像在 Flutter 中任何其餘的地方同樣,要經過特定的 widgets。若是你有一個 TextField 或是 TextFormField,你能夠經過 TextEditingController 來得到用戶輸入:

class _MyFormState extends State<MyForm> { // Create a text controller and use it to retrieve the current value. // of the TextField! final myController = TextEditingController(); 

你能夠在這裏得到更多信息,或是完整的代碼列表: Retrieve the value of a text field,來自 Flutter Cookbook 。

Text field 中的 placeholder 至關於什麼?

在 Flutter 中,你能夠輕易地經過向 Text widget 的裝飾構造器參數重傳遞 InputDecoration 來展現「小提示」,或是佔位符文字:

body: Center( child: TextField( decoration: InputDecoration(hintText: "This is a hint"), ), ) 

我怎麼展現驗證錯誤信息?

就像展現「小提示」同樣,向 Text widget 的裝飾器構造器參數中傳遞一個 InputDecoration

然而,你並不想在一開始就顯示錯誤信息。相反,當用戶輸入了驗證信息,更新狀態,並傳入一個新的 InputDecoration 對象:

class SampleApp extends StatelessWidget { // This widget is the root of your application. 

和硬件、第三方服務以及平臺交互

我怎麼和平臺,以及平臺的原生代碼交互?

Flutter 的代碼並不直接在平臺之下運行,相反,Dart 代碼構建的 Flutter 應用在設備上以原生的方式運行,卻「側步躲開了」平臺提供的 SDK。這意味着,例如,你在 Dart 中發起一個網絡請求,它就直接在 Dart 的上下文中運行。你並不會用上日常在 iOS 或 Android 上使用的原生 API。你的 Flutter 程序仍然被原平生臺的 ViewController 管理做一個 view,可是你並不會直接訪問 ViewController 自身,或是原生框架。

但這並不意味着 Flutter 不能和原生 API,或任何你編寫的原生代碼交互。Flutter 提供了 platform channels ,來和管理你的 Flutter view 的 ViewController 通訊和交互數據。平臺管道本質上是一個異步通訊機制,橋接了 Dart 代碼和宿主 ViewController,以及它運行於的 iOS 框架。你能夠用平臺管道來執行一個原生的函數,或者是從設備的傳感器中獲取數據。

除了直接使用平臺管道以外,你還可使用一系列預先製做好的 plugins。例如,你能夠直接使用插件來訪問相機膠捲或是設備的攝像頭,而沒必要編寫你本身的集成層代碼。你能夠在 Pub 上找到插件,這是一個 Dart 和 Flutter 的開源包倉庫。其中一些包可能會支持集成 iOS 或 Android,或二者都可。

若是你在 Pub 上找不到符合你需求的插件,你能夠本身編寫 ,而且發佈在 Pub 上

我怎麼訪問 GPS 傳感器?

使用 location 社區插件。

我怎麼訪問攝像頭?

image_picker 在訪問攝像頭時很是經常使用。

我怎麼登陸 Facebook?

登陸 Facebook 可使用 flutter_facebook_login 社區插件。

我怎麼使用 Firebase 特性?

大多數 Firebase 特性被  first party plugins 包含了。這些第一方插件由 Flutter 團隊維護:

你也能夠在 Pub 上找到 Firebase 的第三方插件。

我怎建立本身的原生集成層?

若是有一些 Flutter 和社區插件遺漏的平臺相關的特性,能夠根據  developing packages and plugins頁面構建本身的插件。

Flutter 的插件結構,簡要來講,就像 Android 中的 Event bus。你發送一個消息,並讓接受者處理並反饋結果給你。在這種狀況下,接受者就是在 Android 或 iOS 上的原生代碼。

數據庫和本地存儲

我怎麼在 Flutter 中訪問 UserDefaults?

在 iOS 中,你可使用屬性列表來存儲鍵值對的集合,即咱們熟悉的 UserDefaults。

在 Flutter 中,可使用  Shared Preferences plugin 來達到類似的功能。它包裹了 UserDefaluts 以及 Android 上等價的 SharedPreferences 的功能。

CoreData 至關於 Flutter 中的什麼?

在 iOS 中,你經過 CoreData 來存儲結構化的數據。這是一個 SQL 數據庫的上層封裝,讓查詢和關聯模型變得更加簡單。

在 Flutter 中,使用 SQFlite 插件來實現這個功能。

通知

我怎麼推送通知?

在 iOS 中,你須要向蘋果開發者平臺中註冊來容許推送通知。

在 Flutter 中,使用 firebase_messaging 插件來實現這一功能。

更多使用 Firebase Cloud Messaging API 的信息,請參閱 firebase_messaging 插件文檔。

 
 
文章來源: Flutter中文網
相關文章
相關標籤/搜索