Flutter for iOS 開發(fā)者
本文檔適用那些希望將現(xiàn)有 iOS 經(jīng)驗應用于 Flutter 的開發(fā)者。如果你擁有 iOS 開發(fā)基礎蜒什,那么你可以使用這篇文檔開始學習 Flutter 的開發(fā)院峡。
開發(fā) Flutter 時兴使,你的 iOS 經(jīng)驗和技能將會大有裨益,因為 Flutter 依賴于移動操作系統(tǒng)的眾多功能和配置照激。Flutter 是用于為移動設備構建用戶界面的全新方式发魄,但它也有一個插件系統(tǒng)用于和 iOS(及 Android)進行非 UI 任務的通信。如果你是 iOS 開發(fā)專家,則你不必將 Flutter 徹底重新學習一遍励幼。
你可以將此文檔作為 cookbook汰寓,通過跳轉并查找與你的需求最相關的問題。
Views
UIView 相當于 Flutter 中的什么苹粟?
在 iOS 中拆吆,構建 UI 的過程中將大量使用 view 對象里伯。這些對象都是 UIView
的實例。它們可以用作容器來承載其他的 UIView,最終構成你的界面布局涩澡。
在 Flutter 中檬输,你可以粗略地認為 Widget
相當于 UIView
隙姿。Widget 和 iOS 中的控件并不完全等價户敬,但當你試圖去理解 Flutter 是如何工作的時候,你可以認為它們是“聲明和構建 UI 的方法”想帅。
然而场靴,Widget 和 UIView 還是有些區(qū)別的。首先港准,widgets 擁有不同的生存時間:它們一直存在且保持不變旨剥,直到當它們需要被改變。當 widgets 和它們的狀態(tài)被改變時浅缸,F(xiàn)lutter 會構建一顆新的 widgets 樹轨帜。作為對比,iOS 中的 views 在改變時并不會被重新創(chuàng)建衩椒。但是與其說 views 是可變的實例蚌父,不如說它們被繪制了一次,并且直到使用 setNeedsDisplay()
之后才會被重新繪制毛萌。
此外苟弛,不像 UIView,由于不可變性阁将,F(xiàn)lutter 的 widgets 非常輕量膏秫。這是因為它們本身并不是什么控件,也不會被直接繪制出什么做盅,而只是 UI 的描述缤削。
Flutter 包含了 Material 組件庫。這些 widgets 遵循了 Material 設計規(guī)范吹榴。MD 是一個靈活的設計系統(tǒng)僻他,并且為包括 iOS 在內(nèi)的所有系統(tǒng)進行了優(yōu)化。
但是用 Flutter 實現(xiàn)任何的設計語言都非常的靈活和富有表現(xiàn)力腊尚。在 iOS 平臺,你可以使用 Cupertino widgets 來構建遵循了 Apple’s iOS design language 的界面满哪。
我怎么來更新 Widgets婿斥?
在 iOS 上更新 views劝篷,只需要直接改變它們就可以了。在 Flutter 中民宿,widgets 是不可變的娇妓,而且不能被直接更新。你需要去操縱 widget 的 state活鹰。
這也正是有狀態(tài)的和無狀態(tài)的 widget 這一概念的來源哈恰。一個 StatelessWidget
正如它聽起來一樣,是一個沒有附加狀態(tài)的 widget志群。
StatelessWidget
在你構建初始化后不再進行改變的界面時非常有用着绷。
舉個例子,你可能會用一個 UIImageView
來展示你的 logo image
锌云。如果這個 logo 在運行時不會改變荠医,那么你就可以在 Flutter 中使用 StatelessWidget
。
如果你希望在發(fā)起 HTTP 請求時桑涎,依托接收到的數(shù)據(jù)動態(tài)的改變 UI彬向,請使用 StatefulWidget
。當 HTTP 請求結束后攻冷,通知 Flutter 框架 widget 的 State
更新了娃胆,好讓系統(tǒng)來更新 UI。
有狀態(tài)和無狀態(tài)的 widget 之間一個非常重要的區(qū)別是等曼,StatefulWidget
擁有一個 State
對象來存儲它的狀態(tài)數(shù)據(jù)里烦,并在 widget 樹重建時攜帶著它,因此狀態(tài)不會丟失涉兽。
如果你有疑惑招驴,請記住以下規(guī)則:如果一個 widget 在它的 build
方法之外改變(例如,在運行時由于用戶的操作而改變)枷畏,它就是有狀態(tài)的别厘。如果一個 widget 在一次 build 之后永遠不變,那它就是無狀態(tài)的拥诡。但是触趴,即便一個 widget 是有狀態(tài)的,包含它的父親 widget 也可以是無狀態(tài)的渴肉,只要父 widget 本身不響應這些變化冗懦。
下面的例子展示了如何使用一個 StatelessWidget
。一個常見的 StatelessWidget
是 Text
widget仇祭。如果你查看 Text 的實現(xiàn)披蕉,你會發(fā)現(xiàn)它是 StatelessWidget 的子類。
Text(
'I like Flutter!',
style: TextStyle(fontWeight: FontWeight.bold),
);
閱讀上面的代碼,你可能會注意到 Text
widget 并不顯示地攜帶任何狀態(tài)没讲。它通過傳入給它的構造器的數(shù)據(jù)來渲染眯娱,除此之外再無其他。
但是爬凑,如果你希望 I like Flutter
在點擊 FloatingActionButton
時動態(tài)的改變呢徙缴?
為了實現(xiàn)這個,用 StatefulWidget
包裹 Text
widget嘁信,并在用戶點擊按鈕時更新它于样。
舉個例子:
class SampleApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
// Default placeholder text
String textToShow = "I Like Flutter";
void _updateText() {
setState(() {
// update the text
textToShow = "Flutter is Awesome!";
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: Center(child: Text(textToShow)),
floatingActionButton: FloatingActionButton(
onPressed: _updateText,
tooltip: 'Update Text',
child: Icon(Icons.update),
),
);
}
}
我怎么對 widget 布局?我的 Storyboard 在哪潘靖?
在 iOS 中穿剖,你可能會用 Storyboard 文件來組織 views,并對它們設置約束秘豹,或者携御,你可能在 view controller 中使用代碼來設置約束。在 Flutter 中既绕,你通過編寫一個 widget 樹來聲明你的布局啄刹。
下面這個例子展示了如何展示一個帶有 padding 的簡單 widget:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: Center(
child: CupertinoButton(
onPressed: () {
setState(() { _pressedCount += 1; });
},
child: Text('Hello'),
padding: EdgeInsets.only(left: 10.0, right: 10.0),
),
),
);
}
你可以給任何的 widget 添加 padding,這很像 iOS 中約束的功能凄贩。
你可以在 widget catalog 中查看 Flutter 提供的布局誓军。
我怎么在我的約束中添加或移除組件?
在 iOS 中疲扎,你在父 view 中調(diào)用 addSubview()
或在子 view 中調(diào)用 removeFromSuperview()
來動態(tài)地添加或移除子 views昵时。在 Flutter 中,由于 widget 不可變椒丧,所以沒有和 addSubview()
直接等價的東西壹甥。作為替代,你可以向 parent 傳入一個返回 widget 的函數(shù)壶熏,并用一個布爾值來控制子 widget 的創(chuàng)建句柠。
下面這個例子展示了在點擊 FloatingActionButton
時如何動態(tài)地切換兩個 widgets:
class SampleApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
// Default value for toggle
bool toggle = true;
void _toggle() {
setState(() {
toggle = !toggle;
});
}
_getToggleChild() {
if (toggle) {
return Text('Toggle One');
} else {
return CupertinoButton(
onPressed: () {},
child: Text('Toggle Two'),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: Center(
child: _getToggleChild(),
),
floatingActionButton: FloatingActionButton(
onPressed: _toggle,
tooltip: 'Update Text',
child: Icon(Icons.update),
),
);
}
}
我怎么對 widget 做動畫?
在 iOS 中棒假,你通過調(diào)用 animate(withDuration:animations:)
方法來給一個 view 創(chuàng)建動畫溯职。在 Flutter 中,使用動畫庫來包裹 widgets帽哑,而不是創(chuàng)建一個動畫 widget谜酒。
在 Flutter 中,使用 AnimationController
妻枕。這是一個可以暫停僻族、尋找粘驰、停止、反轉動畫的 Animation<double>
類型鹰贵。它需要一個 Ticker
當 vsync 發(fā)生時來發(fā)送信號晴氨,并且在每幀運行時創(chuàng)建一個介于 0 和 1 之間的線性插值(interpolation)。你可以創(chuàng)建一個或多個的 Animation
并附加給一個 controller碉输。
例如,你可能會用 CurvedAnimation
來實現(xiàn)一個 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.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fade Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyFadeTest(title: 'Fade Demo'),
);
}
}
class MyFadeTest extends StatefulWidget {
MyFadeTest({Key key, this.title}) : super(key: key);
final String title;
@override
_MyFadeTest createState() => _MyFadeTest();
}
class _MyFadeTest extends State<MyFadeTest> with TickerProviderStateMixin {
AnimationController controller;
CurvedAnimation curve;
@override
void initState() {
controller = AnimationController(duration: const Duration(milliseconds: 2000), vsync: this);
curve = CurvedAnimation(parent: controller, curve: Curves.easeIn);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Container(
child: FadeTransition(
opacity: curve,
child: FlutterLogo(
size: 100.0,
)
)
)
),
floatingActionButton: FloatingActionButton(
tooltip: 'Fade',
child: Icon(Icons.brush),
onPressed: () {
controller.forward();
},
),
);
}
@override
dispose() {
controller.dispose();
super.dispose();
}
}
更多信息羊壹,請參閱 Animation & Motion widgets, Animations tutorial 以及 Animations overview齐婴。
我該怎么繪圖油猫?
在 iOS 上情妖,你通過 CoreGraphics
來在屏幕上繪制線條和形狀。Flutter 有一套基于 Canvas
類的不同的 API匀哄,還有 CustomPaint
和 CustomPainter
這兩個類來幫助你繪圖犀概。后者實現(xiàn)你在 canvas 上的繪圖算法。
想要學習如何實現(xiàn)一個筆跡畫筆鸥昏,請參考 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 來做到這一點屉更。
我怎么創(chuàng)建自定義的 widgets渤早?
在 iOS 中,你編寫 UIView
的子類,或使用已經(jīng)存在的 view 來重載并實現(xiàn)方法扛芽,以達到特定的功能赋朦。在 Flutter 中壹将,你會組合(composing)多個小的 widgets 來構建一個自定義的 widget(而不是擴展它)妇菱。
舉個例子,如果你要構建一個 CustomButton
暴区,并在構造器中傳入它的 label闯团?那就組合 RaisedButton
和 label,而不是擴展 RaisedButton
仙粱。
class CustomButton extends StatelessWidget {
final String label;
CustomButton(this.label);
@override
Widget build(BuildContext context) {
return RaisedButton(onPressed: () {}, child: Text(label));
}
}
然后就像你使用其他任何 Flutter 的 widget 一樣房交,使用你的 CustomButton:
@override
Widget build(BuildContext context) {
return Center(
child: CustomButton("Hello"),
);
}
導航
我怎么在不同頁面之間跳轉?
在 iOS 中伐割,你可以使用管理了 view controller 棧的 UINavigationController
來在不同的 view controller 之間跳轉候味。
Flutter 也有類似的實現(xiàn),使用了 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恶复。對系統(tǒng)級別的 App 來說怜森,這個 scheme 取決于 App速挑。為了在 Flutter 中實現(xiàn)這個功能,你可以創(chuàng)建一個原生平臺的整合層副硅,或者使用現(xiàn)有的 plugin姥宝,例如 url_launcher。
線程和異步
我怎么編寫異步的代碼恐疲?
Dart 是單線程執(zhí)行模型腊满,但是它支持 Isolate
(一種讓 Dart 代碼運行在其他線程的方式)、事件循環(huán)和異步編程培己。除非你自己創(chuàng)建一個 Isolate
碳蛋,否則你的 Dart 代碼永遠運行在 UI 線程,并由 event loop 驅(qū)動省咨。Flutter 的 event loop 和 iOS 中的 main loop 相似——Looper
是附加在主線程上的肃弟。
Dart 的單線程模型并不意味著你寫的代碼一定是阻塞操作,從而卡住 UI零蓉。相反愕乎,使用 Dart 語言提供的異步工具,例如 async
/ await
壁公,來實現(xiàn)異步操作。
舉個例子绅项,你可以使用 async
/ await
來讓 Dart 幫你做一些繁重的工作紊册,編寫網(wǎng)絡請求代碼而不會掛起 UI:
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = json.decode(response.body);
});
}
一旦 await
到網(wǎng)絡請求完成,通過調(diào)用 setState()
來更新 UI快耿,這會觸發(fā) widget 子樹的重建囊陡,并更新相關數(shù)據(jù)。
下面的例子展示了異步加載數(shù)據(jù)掀亥,并用 ListView
展示出來:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
List widgets = [];
@override
void initState() {
super.initState();
loadData();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: ListView.builder(
itemCount: widgets.length,
itemBuilder: (BuildContext context, int position) {
return getRow(position);
}));
}
Widget getRow(int i) {
return Padding(
padding: EdgeInsets.all(10.0),
child: Text("Row ${widgets[i]["title"]}")
);
}
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = json.decode(response.body);
});
}
}
更多關于在后臺工作的信息撞反,以及 Flutter 和 iOS 的區(qū)別,請參考下一章節(jié)搪花。
你是怎么把工作放到后臺線程的遏片?
由于 Flutter 是單線程并且跑著一個 event loop 的(就像 Node.js 那樣),你不必為線程管理或是開啟后臺線程而操心撮竿。如果你正在做 I/O 操作吮便,如訪問磁盤或網(wǎng)絡請求,安全地使用 async
/ await
就完事了幢踏。如果髓需,在另外的情況下,你需要做讓 CPU 執(zhí)行繁忙的計算密集型任務房蝉,你需要使用 Isolate
來避免阻塞 event loop僚匆。
對于 I/O 操作微渠,通過關鍵字 async
,把方法聲明為異步方法咧擂,然后通過await
關鍵字等待該異步方法執(zhí)行完成(譯者語:這和javascript中是相同的):
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = json.decode(response.body);
});
}
這就是對諸如網(wǎng)絡請求或數(shù)據(jù)庫訪問等 I/O 操作的典型做法逞盆。
然而,有時候你需要處理大量的數(shù)據(jù)屋确,這會導致你的 UI 掛起纳击。在 Flutter 中,使用 Isolate
來發(fā)揮多核心 CPU 的優(yōu)勢來處理那些長期運行或是計算密集型的任務攻臀。
Isolates 是分離的運行線程焕数,并且不和主線程的內(nèi)存堆共享內(nèi)存。這意味著你不能訪問主線程中的變量刨啸,或者使用 setState()
來更新 UI堡赔。正如它們的名字一樣,Isolates 不能共享內(nèi)存设联。
下面的例子展示了一個簡單的 isolate善已,是如何把數(shù)據(jù)返回給主線程來更新 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()
是一個運行于自己獨立執(zhí)行線程上的 Isolate
离例。在 isolate 里换团,你可以執(zhí)行 CPU 密集型任務(例如解析一個龐大的 json),或是計算密集型的數(shù)學操作宫蛆,如加密或信號處理等艘包。
你可以運行下面的完整例子:
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 {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
List widgets = [];
@override
void initState() {
super.initState();
loadData();
}
showLoadingDialog() {
if (widgets.length == 0) {
return true;
}
return false;
}
getBody() {
if (showLoadingDialog()) {
return getProgressDialog();
} else {
return getListView();
}
}
getProgressDialog() {
return Center(child: CircularProgressIndicator());
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: getBody());
}
ListView getListView() => ListView.builder(
itemCount: widgets.length,
itemBuilder: (BuildContext context, int position) {
return getRow(position);
});
Widget getRow(int i) {
return Padding(padding: EdgeInsets.all(10.0), child: Text("Row ${widgets[i]["title"]}"));
}
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;
}
}
我怎么發(fā)起網(wǎng)絡請求?
在 Flutter 中耀盗,使用流行的 http package 做網(wǎng)絡請求非常簡單想虎。它把你可能需要自己做的網(wǎng)絡請求操作抽象了出來,讓發(fā)起請求變得簡單叛拷。
要使用 http
包舌厨,在 pubspec.yaml
中把它添加為依賴:
dependencies:
...
http: ^0.11.3+16
發(fā)起網(wǎng)絡請求,在 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 更新狀態(tài)你雌,并在結束后隱去。
在下面的例子中,build 函數(shù)被拆分成三個函數(shù)婿崭。如果 showLoadingDialog()
是 true
(當 widgets.length == 0
時)拨拓,則渲染 ProgressIndicator
。否則氓栈,當數(shù)據(jù)從網(wǎng)絡請求中返回時渣磷,渲染 ListView
。
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
List widgets = [];
@override
void initState() {
super.initState();
loadData();
}
showLoadingDialog() {
return widgets.length == 0;
}
getBody() {
if (showLoadingDialog()) {
return getProgressDialog();
} else {
return getListView();
}
}
getProgressDialog() {
return Center(child: CircularProgressIndicator());
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: getBody());
}
ListView getListView() => ListView.builder(
itemCount: widgets.length,
itemBuilder: (BuildContext context, int position) {
return getRow(position);
});
Widget getRow(int i) {
return Padding(padding: EdgeInsets.all(10.0), child: Text("Row ${widgets[i]["title"]}"));
}
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = json.decode(response.body);
});
}
}
工程結構授瘦、本地化醋界、依賴和資源
我怎么在 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');
}
對于圖片伞插,F(xiàn)lutter 像 iOS 一樣,遵循了一個簡單的基于像素密度的格式争便。Image assets 可能是 1.0x
2.0x
3.0x
或是其他的任何倍數(shù)。這些所謂的 devicePixelRatio
傳達了物理像素到單個邏輯像素的比率断医。
Assets 可以被放置到任何屬性文件夾中——Flutter 并沒有預先定義的文件結構始花。在 pubspec.yaml
文件中聲明 assets (和位置),然后 Flutter 會把他們識別出來孩锡。
舉個例子,要把一個叫 my_icon.png
的圖片放到 Flutter 工程中亥贸,你可能想要把存儲它的文件夾叫做 images
躬窜。把基礎圖片(1.0x)放置到 images
文件夾中,并把其他變體放置在子文件夾中炕置,并接上合適的比例系數(shù):
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 中直接使用:
@override
Widget build(BuildContext context) {
return Image.asset("images/my_image.png");
}
更多細節(jié),參見 Adding Assets and Images in Flutter朴摊。
我在哪里放置字符串默垄?我怎么做本地化?
不像 iOS 擁有一個 Localizable.strings
文件甚纲,F(xiàn)lutter 目前并沒有一個用于處理字符串的系統(tǒng)口锭。目前,最佳實踐是把你的文本拷貝到靜態(tài)區(qū),并在這里訪問鹃操。例如:
class Strings {
static String welcomeMessage = "Welcome To Flutter";
}
并且這樣訪問你的字符串:
Text(Strings.welcomeMessage)
默認情況下韭寸,F(xiàn)lutter 只支持美式英語字符串。如果你要支持其他語言荆隘,請引入 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 支持哪些地區(qū)褒脯。上面的例子使用了一個 MaterialApp
,所以它既有 GlobalWidgetsLocalizations
用于基礎 widgets仪壮,也有 MaterialWidgetsLocalizations
用于 Material wigets 的本地化憨颠。如果你使用 WidgetsApp
,則無需包括后者积锅。注意爽彤,這兩個代理雖然包括了“默認”值,但如果你想讓你的 App 本地化缚陷,你仍需要提供一或多個代理作為你的 App 本地化副本适篙。
當初始化時,WidgetsApp
或 MaterialApp
會使用你指定的代理為你創(chuàng)建一個 Localizations
widget箫爷。Localizations
widget 可以隨時從當前上下文中訪問設備的地點嚷节,或者使用 Window.locale
。
要訪問本地化文件虎锚,使用 Localizations.of()
方法來訪問提供代理的特定本地化類硫痰。如需翻譯,使用 intl_translation
包來取出翻譯副本到 arb 文件中窜护。把它們引入 App 中效斑,并用 intl
來使用它們。
更多 Flutter 中國際化和本地化的細節(jié)柱徙,請訪問 internationalization guide 缓屠,那里有不使用 intl
包的示例代碼。
注意护侮,在 Flutter 1.0 beta 2 之前敌完,在 Flutter 中定義的 assets 不能在原生一側被訪問。原生定義的資源在 Flutter 中也不可用羊初,因為它們在獨立的文件夾中滨溉。
Cocoapods 相當于什么?我該如何添加依賴?
在 iOS 中业踏,你把依賴添加到 Podfile
中禽炬。Flutter 使用 Dart 構建系統(tǒng)和 Pub 包管理器來處理依賴。這些工具將本機 Android 和 iOS 包裝應用程序的構建委派給相應的構建系統(tǒng)勤家。
如果你的 Flutter 工程中的 iOS 文件夾中擁有 Podfile腹尖,請僅在你為每個平臺集成時使用它》ゲ保總體來說热幔,使用 pubspec.yaml
來在 Flutter 中聲明外部依賴。一個可以找到優(yōu)秀 Flutter 包的地方是 Pub讼庇。
ViewControllers
ViewController 相當于 Flutter 中的什么绎巨?
在 iOS 中,一個 ViewController 代表了用戶界面的一部分蠕啄,最常用于一個屏幕场勤,或是其中一部分。它們被組合在一起用于構建復雜的用戶界面歼跟,并幫助你拆分 App 的 UI和媳。在 Flutter 中,這一任務回落到了 widgets 中哈街。就像在界面導航部分提到的一樣留瞳,一個屏幕也是被 widgets 來表示的,因為“萬物皆 widget骚秦!”她倘。使用 Navigator
在 Route
之間跳轉,或者渲染相同數(shù)據(jù)的不同狀態(tài)作箍。
我該怎么監(jiān)聽 iOS 中的生命周期事件硬梁?
在 iOS 中,你可以重寫 ViewController
中的方法來捕獲它的視圖的生命周期胞得,或者在 AppDelegate
中注冊生命周期的回調(diào)函數(shù)荧止。在 Flutter 中沒有這兩個概念,但你可以通過 hook WidgetsBinding
觀察者來監(jiān)聽生命周期事件懒震,并監(jiān)聽 didChangeAppLifecycleState()
的變化事件。
可觀察的生命周期事件有:
-
inactive
- 應用處于不活躍的狀態(tài)嗤详,并且不會接受用戶的輸入个扰。這個事件僅工作在 iOS 平臺,在 Android 上沒有等價的事件葱色。 -
paused
- 應用暫時對用戶不可見递宅,雖然不接受用戶輸入,但是是在后臺運行的。 -
resumed
- 應用可見办龄,也響應用戶的輸入烘绽。 -
suspending
- 應用暫時被掛起,在 iOS 上沒有這一事件俐填。
更多關于這些狀態(tài)的細節(jié)和含義安接,請參見 AppLifecycleStatus
documentation 。
布局
UITableView 和 UICollectionView 相當于 Flutter 中的什么英融?
在 iOS 中盏檐,你可能用 UITableView 或 UICollectionView 來展示一個列表。在 Flutter 中驶悟,你可以用 ListView
來達到相似的實現(xiàn)胡野。在 iOS 中,你通過代理方法來確定行數(shù)痕鳍,每一個 index path 的單元格硫豆,以及單元格的尺寸。
由于 Flutter 中 widget 的不可變特性笼呆,你需要向 ListView
傳遞一個 widget 列表熊响,F(xiàn)lutter 會確保滾動是快速且流暢的。
import 'package:flutter/material.dart';
void main() {
runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: ListView(children: _getListData()),
);
}
_getListData() {
List<Widget> widgets = [];
for (int i = 0; i < 100; i++) {
widgets.add(Padding(padding: EdgeInsets.all(10.0), child: Text("Row $i")));
}
return widgets;
}
}
我怎么知道列表的哪個元素被點擊了抄邀?
iOS 中耘眨,你通過 tableView:didSelectRowAtIndexPath:
代理方法來實現(xiàn)。在 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.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: ListView(children: _getListData()),
);
}
_getListData() {
List<Widget> widgets = [];
for (int i = 0; i < 100; i++) {
widgets.add(GestureDetector(
child: Padding(
padding: EdgeInsets.all(10.0),
child: Text("Row $i"),
),
onTap: () {
print('row tapped');
},
));
}
return widgets;
}
}
我怎么動態(tài)地更新 ListView剔难?
在 iOS 中,你改變列表的數(shù)據(jù)奥喻,并通過 reloadData()
方法來通知 table 或是 collection view偶宫。
在 Flutter 中,如果你想通過 setState()
方法來更新 widget 列表环鲤,你會很快發(fā)現(xiàn)你的數(shù)據(jù)展示并沒有變化纯趋。這是因為當 setState()
被調(diào)用時,F(xiàn)lutter 渲染引擎會去檢查 widget 樹來查看是否有什么地方被改變了冷离。當它得到你的 ListView
時吵冒,它會使用一個 ==
判斷,并且發(fā)現(xiàn)兩個 ListView
是相同的西剥。沒有什么東西是變了的痹栖,因此更新不是必須的。
一個更新 ListView
的簡單方法是瞭空,在 setState()
中創(chuàng)建一個新的 list揪阿,并把舊 list 的數(shù)據(jù)拷貝給新的 list疗我。雖然這樣很簡單,但當數(shù)據(jù)集很大時南捂,并不推薦這樣做:
import 'package:flutter/material.dart';
void main() {
runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
List widgets = [];
@override
void initState() {
super.initState();
for (int i = 0; i < 100; i++) {
widgets.add(getRow(i));
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: ListView(children: widgets),
);
}
Widget getRow(int i) {
return GestureDetector(
child: Padding(
padding: EdgeInsets.all(10.0),
child: Text("Row $i"),
),
onTap: () {
setState(() {
widgets = List.from(widgets);
widgets.add(getRow(widgets.length + 1));
print('row $i');
});
},
);
}
}
一個推薦的吴裤、高效的且有效的做法是,使用 ListView.Builder
來構建列表溺健。這個方法在你想要構建動態(tài)列表麦牺,或是列表擁有大量數(shù)據(jù)時會非常好用。
import 'package:flutter/material.dart';
void main() {
runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
List widgets = [];
@override
void initState() {
super.initState();
for (int i = 0; i < 100; i++) {
widgets.add(getRow(i));
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: ListView.builder(
itemCount: widgets.length,
itemBuilder: (BuildContext context, int position) {
return getRow(position);
},
),
);
}
Widget getRow(int i) {
return GestureDetector(
child: Padding(
padding: EdgeInsets.all(10.0),
child: Text("Row $i"),
),
onTap: () {
setState(() {
widgets.add(getRow(widgets.length + 1));
print('row $i');
});
},
);
}
}
與創(chuàng)建一個 “ListView” 不同矿瘦,創(chuàng)建一個 ListView.builder
接受兩個主要參數(shù):列表的初始長度枕面,和一個 ItemBuilder
方法。
ItemBuilder
方法和 cellForItemAt
代理方法非常類似缚去,它接受一個位置潮秘,并且返回在這個位置上你希望渲染的 cell。
最后易结,也是最重要的枕荞,注意 onTap()
函數(shù)里并沒有重新創(chuàng)建一個 list,而是 .add
了一個 widget搞动。
ScrollView 相當于 Flutter 里的什么躏精?
在 iOS 中,你給 view 包裹上 ScrollView
來允許用戶在需要時滾動你的內(nèi)容鹦肿。
在 Flutter 中矗烛,最簡單的方法是使用 ListView
widget。它表現(xiàn)得既和 iOS 中的 ScrollView
一致箩溃,也能和 TableView
一致瞭吃,因為你可以給它的 widget 做垂直排布:
@override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
Text('Row One'),
Text('Row Two'),
Text('Row Three'),
Text('Row Four'),
],
);
}
更多關于在 Flutter 總如何排布 widget 的文檔,請參閱 layout tutorial涣旨。
手勢檢測及觸摸事件處理
我怎么給 Flutter 的 widget 添加一個點擊監(jiān)聽者歪架?
在 iOS 中,你給一個 view 添加 GestureRecognizer
來處理點擊事件霹陡。在 Flutter 中和蚪,有兩種方法來添加點擊監(jiān)聽者:
-
如果 widget 本身支持事件監(jiān)測,直接傳遞給它一個函數(shù)烹棉,并在這個函數(shù)里實現(xiàn)響應方法攒霹。例如,
RaisedButton
widget 擁有一個RaisedButton
參數(shù):@override Widget build(BuildContext context) { return RaisedButton( onPressed: () { print("click"); }, child: Text("Button"), ); }
-
如果 widget 本身不支持事件監(jiān)測浆洗,則在外面包裹一個 GestureDetector催束,并給它的 onTap 屬性傳遞一個函數(shù):
class SampleApp extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Center( child: GestureDetector( child: FlutterLogo( size: 200.0, ), onTap: () { print("tap"); }, ), ), ); } }
我怎么處理 widget 上的其他手勢?
使用 GestureDetector
你可以監(jiān)聽更廣闊范圍內(nèi)的手勢辅髓,比如:
- Tapping
-
onTapDown
— 在特定位置輕觸手勢接觸了屏幕泣崩。 -
onTapUp
— 在特定位置產(chǎn)生了一個輕觸手勢,并停止接觸屏幕洛口。 -
onTap
— 產(chǎn)生了一個輕觸手勢矫付。 -
onTapCancel
— 觸發(fā)了onTapDown
但沒能觸發(fā) tap。
-
- Double tapping
-
onDoubleTap
— 用戶在同一個位置快速點擊了兩下屏幕第焰。
-
- Long pressing
-
onLongPress
— 用戶在同一個位置長時間接觸屏幕买优。
-
- Vertical dragging
-
onVerticalDragStart
— 接觸了屏幕,并且可能會垂直移動挺举。 -
onVerticalDragUpdate
— 接觸了屏幕杀赢,并繼續(xù)在垂直方向移動。 -
onVerticalDragEnd
— 之前接觸了屏幕并垂直移動湘纵,并在停止接觸屏幕前以某個垂直的速度移動脂崔。
-
- Horizontal dragging
-
onHorizontalDragStart
— 接觸了屏幕,并且可能會水平移動梧喷。 -
onHorizontalDragUpdate
— 接觸了屏幕砌左,并繼續(xù)在水平方向移動。 -
onHorizontalDragEnd
— 之前接觸屏幕并水平移動的觸摸點與屏幕分離铺敌。
-
下面這個例子展示了一個 GestureDetector
是如何在雙擊時旋轉 Flutter 的 logo 的:
AnimationController controller;
CurvedAnimation curve;
@override
void initState() {
controller = AnimationController(duration: const Duration(milliseconds: 2000), vsync: this);
curve = CurvedAnimation(parent: controller, curve: Curves.easeIn);
}
class SampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: GestureDetector(
child: RotationTransition(
turns: curve,
child: FlutterLogo(
size: 200.0,
)),
onDoubleTap: () {
if (controller.isCompleted) {
controller.reverse();
} else {
controller.forward();
}
},
),
),
);
}
}
主題和文字
我怎么給 App 設置主題汇歹?
Flutter 實現(xiàn)了一套漂亮的 MD 組件,并且開箱可用偿凭。它接管了一大堆你需要的樣式和主題产弹。
為了充分發(fā)揮你的 App 中 MD 組件的優(yōu)勢,聲明一個頂級 widget弯囊,MaterialApp痰哨,用作你的 App 入口。MaterialApp 是一個便利組件常挚,包含了許多 App 通常需要的 MD 風格組件作谭。它通過一個 WidgetsApp 添加了 MD 功能來實現(xiàn)。
但是 Flutter 足夠地靈活和富有表現(xiàn)力來實現(xiàn)任何其他的設計語言奄毡。在 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(),
);
}
}
我怎么給 Text widget 設置自定義字體罐寨?
在 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 的樣式元素設置自定義值拧簸。Text
widget 接受一個 TextStyle
對象,你可以指定許多參數(shù)男窟,比如:
color
decoration
decorationColor
decorationStyle
fontFamily
fontSize
fontStyle
fontWeight
hashCode
height
inherit
letterSpacing
textBaseline
wordSpacing
表單輸入
Flutter 中表單怎么工作狡恬?我怎么拿到用戶的輸入?
我們已經(jīng)提到 Flutter 使用不可變的 widget蝎宇,并且狀態(tài)是分離的弟劲,你可能會好奇在這種情境下怎么處理用戶的輸入。在 iOS 中姥芥,你經(jīng)常在需要提交數(shù)據(jù)時查詢組件當前的狀態(tài)或動作兔乞,但這在 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();
@override
void dispose() {
// Clean up the controller when disposing of the Widget.
myController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Retrieve Text Input'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: TextField(
controller: myController,
),
),
floatingActionButton: FloatingActionButton(
// When the user presses the button, show an alert dialog with the
// text the user has typed into our text field.
onPressed: () {
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
// Retrieve the text the user has typed in using our
// TextEditingController
content: Text(myController.text),
);
},
);
},
tooltip: 'Show me the value!',
child: Icon(Icons.text_fields),
),
);
}
}
你可以在這里獲得更多信息淡溯,或是完整的代碼列表: Retrieve the value of a text field,來自 Flutter Cookbook 簿训。
Text field 中的 placeholder 相當于什么咱娶?
在 Flutter 中,你可以輕易地通過向 Text widget 的裝飾構造器參數(shù)重傳遞 InputDecoration
來展示“小提示”强品,或是占位符文字:
body: Center(
child: TextField(
decoration: InputDecoration(hintText: "This is a hint"),
),
)
我怎么展示驗證錯誤信息膘侮?
就像展示“小提示”一樣,向 Text widget 的裝飾器構造器參數(shù)中傳遞一個 InputDecoration
的榛。
然而琼了,你并不想在一開始就顯示錯誤信息。相反夫晌,當用戶輸入了驗證信息雕薪,更新狀態(tài)昧诱,并傳入一個新的 InputDecoration
對象:
class SampleApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
String _errorText;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: Center(
child: TextField(
onSubmitted: (String text) {
setState(() {
if (!isEmail(text)) {
_errorText = 'Error: This is not an email';
} else {
_errorText = null;
}
});
},
decoration: InputDecoration(hintText: "This is a hint", errorText: _getErrorText()),
),
),
);
}
_getErrorText() {
return _errorText;
}
bool isEmail(String em) {
String emailRegexp =
r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';
RegExp regExp = RegExp(p);
return regExp.hasMatch(em);
}
}
和硬件、第三方服務以及平臺交互
我怎么和平臺所袁,以及平臺的原生代碼交互鳄哭?
Flutter 的代碼并不直接在平臺之下運行,相反纲熏,Dart 代碼構建的 Flutter 應用在設備上以原生的方式運行,卻“側步躲開了”平臺提供的 SDK锄俄。這意味著局劲,例如,你在 Dart 中發(fā)起一個網(wǎng)絡請求奶赠,它就直接在 Dart 的上下文中運行鱼填。你并不會用上平常在 iOS 或 Android 上使用的原生 API。你的 Flutter 程序仍然被原生平臺的 ViewController
管理作一個 view毅戈,但是你并不會直接訪問 ViewController
自身苹丸,或是原生框架。
但這并不意味著 Flutter 不能和原生 API苇经,或任何你編寫的原生代碼交互赘理。Flutter 提供了 platform channels ,來和管理你的 Flutter view 的 ViewController 通信和交互數(shù)據(jù)扇单。平臺管道本質(zhì)上是一個異步通信機制商模,橋接了 Dart 代碼和宿主 ViewController,以及它運行于的 iOS 框架蜘澜。你可以用平臺管道來執(zhí)行一個原生的函數(shù)施流,或者是從設備的傳感器中獲取數(shù)據(jù)。
除了直接使用平臺管道之外鄙信,你還可以使用一系列預先制作好的 plugins瞪醋。例如,你可以直接使用插件來訪問相機膠卷或是設備的攝像頭装诡,而不必編寫你自己的集成層代碼银受。你可以在 Pub 上找到插件,這是一個 Dart 和 Flutter 的開源包倉庫鸦采。其中一些包可能會支持集成 iOS 或 Android蚓土,或兩者均可。
如果你在 Pub 上找不到符合你需求的插件赖淤,你可以自己編寫 蜀漆,并且發(fā)布在 Pub 上。
我怎么訪問 GPS 傳感器咱旱?
使用 location
社區(qū)插件确丢。
我怎么訪問攝像頭绷耍?
image_picker
在訪問攝像頭時非常常用。
我怎么登錄 Facebook鲜侥?
登錄 Facebook 可以使用 flutter_facebook_login
社區(qū)插件褂始。
我怎么使用 Firebase 特性?
大多數(shù) Firebase 特性被 first party plugins 包含了描函。這些第一方插件由 Flutter 團隊維護:
-
firebase_admob
for Firebase AdMob -
firebase_analytics
for Firebase Analytics -
firebase_auth
for Firebase Auth -
firebase_core
for Firebase’s Core package -
firebase_database
for Firebase RTDB -
firebase_storage
for Firebase Cloud Storage -
firebase_messaging
for Firebase Messaging (FCM) -
cloud_firestore
for Firebase Cloud Firestore
你也可以在 Pub 上找到 Firebase 的第三方插件崎苗。
我怎創(chuàng)建自己的原生集成層?
如果有一些 Flutter 和社區(qū)插件遺漏的平臺相關的特性舀寓,可以根據(jù) developing packages and plugins 頁面構建自己的插件胆数。
Flutter 的插件結構,簡要來說互墓,就像 Android 中的 Event bus必尼。你發(fā)送一個消息,并讓接受者處理并反饋結果給你篡撵。在這種情況下判莉,接受者就是在 Android 或 iOS 上的原生代碼。
數(shù)據(jù)庫和本地存儲
我怎么在 Flutter 中訪問 UserDefaults育谬?
在 iOS 中券盅,你可以使用屬性列表來存儲鍵值對的集合,即我們熟悉的 UserDefaults膛檀。
在 Flutter 中渗饮,可以使用 Shared Preferences plugin 來達到相似的功能。它包裹了 UserDefaluts
以及 Android 上等價的 SharedPreferences
的功能宿刮。
CoreData 相當于 Flutter 中的什么互站?
在 iOS 中,你通過 CoreData 來存儲結構化的數(shù)據(jù)僵缺。這是一個 SQL 數(shù)據(jù)庫的上層封裝胡桃,讓查詢和關聯(lián)模型變得更加簡單。
在 Flutter 中磕潮,使用 SQFlite 插件來實現(xiàn)這個功能翠胰。
通知
我怎么推送通知?
在 iOS 中自脯,你需要向蘋果開發(fā)者平臺中注冊來允許推送通知之景。
在 Flutter 中,使用 firebase_messaging
插件來實現(xiàn)這一功能膏潮。
更多使用 Firebase Cloud Messaging API 的信息锻狗,請參閱 firebase_messaging
插件文檔。