官方英文原文: flutter.io/flutter-for…
提示:由於篇幅很長,所以分為上下兩篇,給Android開發者的Flutter指南 (下)已經翻譯完成,感興趣的同學可以看看
一、對應於 View
Flutter
中可以將Widget
看成View
,但是又不能當成是Andriod
中的View
,可以類比的理解。
與View
不同的是,Widget
的壽命不同,它們是不可變的,直到它們需要改變時才會退出,而每當它們的狀態發生改變時,Flutter
框架都會建立新的Widget
例項,相比之下,View
只會繪製一次直到下一次呼叫invalidate
。
Flutter
中Widget
是很輕量的,部分原因歸咎於它們的不可變性,因為它們本身不是View
檢視,且不會直接繪製任何東西,而是用於描述UI
1. 如何更新Widget
在Android
中,可以直接改變view
以更新它們,但是在Flutter
中,widget
是不可變的,不能直接更新,而需要通過Widget state
來更新。
這就是StatelessWidget
和StatefulWidget
的來源
-
StatelessWidget
一個沒有狀態資訊的Widget
。當描述的使用者介面部分不依賴於物件中的配置資訊時,StatelessWidgets
會很有用。這就類似於在Android
中使用ImageVIew
顯示logo
,這個logo
並不需要在執行期間做任何改變,對應的在Flutter
中就使用StatelessWidget
。 -
StatefulWidget
如果你想要基於網路請求得到的資料動態改變UI
,那麼就使用StatefulWidget
,並且告訴Flutter
框架,這個Widget
的State
(狀態)已經發生改變,可以更新Widget
了。
值得注意的是,在Flutter
核心中,StatelessWidget
和StatefulWidget
兩者的行為是相同的,它們都會重建每一幀,不同的是StatefulWidget
包含了一個State
物件,用於儲存跨幀資料,以及恢復幀資料。
如果你心存疑惑,那麼記住這個規則:如果因為使用者互動,控制元件需要改變的話,那麼他就是stateful
,而如果控制元件需要響應改變,但是父容器控制元件並不需要自己響應改變,那麼這個父容器依然可以是stateless
的。
以下示例展示瞭如何使用StatelessWidget
,一個普遍的StatelessWidget
就是Text
控制元件,如果你檢視了Text
的實現,你會發現是StatelessWidget
的子類。
Text(
'I like Flutter!',
style: TextStyle(fontWeight: FontWeight.bold),
);
複製程式碼
如你所見,Text
控制元件並沒有與之關聯的狀態資訊,它只是渲染了構建它時傳入的的資料,然後沒了。但是如果你想要讓'I like Flutter!'
可以動態改變,比方說響應FloatingActionButton
的點選事件,那麼可以將Text
包含在一個StatefulWidget
中,然後在使用者點選按鈕時更新它。如下示例:
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> {
// 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),
),
);
}
}
複製程式碼
2. 如何佈局Widget
在Android
中,佈局寫在xml
中,而在Flutter
中,佈局就是控制元件樹(Widget tree
),以下示例描述瞭如何佈局一個帶內邊距的控制元件。
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: Center(
child: MaterialButton(
onPressed: () {},
child: Text('Hello'),
padding: EdgeInsets.only(left: 10.0, right: 10.0),
),
),
);
}
複製程式碼
可以檢視Flutter
提供的 控制元件目錄
3. 如何新增和刪除佈局元件
在Android
中,可以呼叫父容器的addChild
和removeChild
動態新增和刪除子view
,而在flutter
中,因為控制元件都是不可變的,因此沒有直接與addChild
行對應的功能,但是可以給父容器傳入一個返回控制元件的函式,然後通過一個boolean
標記來控制子控制元件的建立。
例如,以下示例演示瞭如何在點選FloatingActionButton
時在兩個控制元件間切換的功能:
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> {
// Default value for toggle
bool toggle = true;
void _toggle() {
setState(() {
toggle = !toggle;
});
}
_getToggleChild() {
if (toggle) {
return Text('Toggle One');
} else {
return MaterialButton(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),
),
);
}
}
複製程式碼
4. 如何給控制元件新增動畫
在Android
中,你可以通過xml
來建立動畫,或者呼叫view
的animate()
方法。而在Flutter
中,則是將控制元件包裹在動畫控制元件內,然後使用動畫庫執行動畫。
在Flutter
中,使用AnimationController
(它是個Animation<double>
)可以暫停、定位、停止以及反轉動畫。它需要一個Ticker
用於示意(signal
)vsync
在何時產生,然後在它所執行的幀上產生一個值在[0,1]
之間的線性插值,你可以建立一個或多個Animation
,然後將他們繫結到控制器上。
比方說,你可能使用一個CurvedAnimation
來沿著插值曲線執行動畫,這種情況下,控制器就是動畫進度的“master”
資源,而CurvedAnimation
則用於計算、替換控制器預設線性動作的曲線,正如Flutter
中的Widget
一樣,動畫也是組合起來工作的。
當構建控制元件樹時,你為控制元件的動畫屬性指定了Animation
,比方說FadeTransition
的不透明度(opacity
),接著就是告訴控制器來執行動畫了。以下示例描述瞭如何編寫一個在點選FloatingActionButton
時如何將控制元件淡化(fade
)成logo
的FadeTransition
。
import 'package:flutter/material.dart';
void main() {
runApp(FadeAppTest());
}
class FadeAppTest 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();
},
),
);
}
}
複製程式碼
更多資訊,請檢視 Animation & Motion widgets 、Animations tutorial 和 Animations overview..
5. 如何使用Canvas
畫圖?
在Android
中,你會通過Canvas
和Drawable
來繪製圖形,Flutter
中也有個類似的Canvas API
,因為它們都基於底層的渲染引擎Skia
,因此在使用Flutter
的Canvas
畫圖操作對於Android
開發者來說是件非常熟悉的事情。
Flutter
中有兩個幫助繪圖的類:CustomPaint
和CustomPainter
,其中後者用於實現你的繪圖邏輯。
學習如何在Flutter
上實現簽名畫板,可以檢視 Collin在StackOverflow的回答
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(home: DemoApp()));
class DemoApp extends StatelessWidget {
Widget build(BuildContext context) => Scaffold(body: Signature());
}
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),
);
}
}
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;
}
複製程式碼
6. 如何自定義控制元件?
在Android
中,典型的方式就是繼承VIew
,或者使用已有的View
控制元件,然後複寫相關方法以實現期望的行為。而在flutter
中,則是通過組合小控制元件的方式自定義View
,而不是繼承它們,這在某種程度上跟通過ViewGroup
實現自定義控制元件的方式很像,因為所有小元件都是已經存在的,你只是將他們組合起來以提供不一樣的行為,比如,只是自定義佈局邏輯。
例如,你會怎樣實現一個在構造器中傳入標題的CustomButton
呢?組合RaisedButton
和Text
,而不是繼承RaisedButton
:
class CustomButton extends StatelessWidget {
final String label;
CustomButton(this.label);
@override
Widget build(BuildContext context) {
return RaisedButton(onPressed: () {}, child: Text(label));
}
}
複製程式碼
然後就可以使用CustomButton
了,只需要新增到任意Flutter
控制元件中即可:
@override
Widget build(BuildContext context) {
return Center(
child: CustomButton("Hello"),
);
}
複製程式碼
二、對應於 Intent
1. Flutter中與Intent相對應的是什麼?
在Android
中,Intent
有兩個主要用途:用於activity
間的跳轉、用於元件間的通訊。而在Flutter
中,沒有Intent
這個概念,雖然你依然可以通過本地整合(native integrations
(使用外掛))來啟動Intent
。
Flutter
也沒有與activity
、fragment
直接對應的元件,而是使用Navigator
、Route
來進行螢幕間的切換,這跟activity
類似。
Route
是應用螢幕和頁面的抽象,而Navigator
則是一個管理Route
的控制元件。可以粗略的將Route
看成activity
,但是它們含義不同。Navigator
通過push
和pop
(可看成壓棧和出棧)Route
來切換螢幕,Navigator
工作原理可看成一個棧,push
表示向前切換,pop
表示返回。
在Android
中,需要在AndroidManifest.xml
中宣告activity
,而在Flutter
中,你有以下頁面切換選擇:
- 指定一個包含所有
Route
名字的Map
(MaterialApp
) - 直接切換到
Route
(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'),
},
));
}
複製程式碼
而如下則是通過將Route
的名字直接push
至Navigator
的方式:
Navigator.of(context).pushNamed('/b');
複製程式碼
另一個使用Intent
的使用場景是呼叫外部元件,比如相機、檔案選擇器,對於這種情況,你需要建立一個本地平臺的整合(native platform integration
),或者使用已有的外掛;
關於如何構建本地平臺整合,請檢視 Developing Packages and Plugins..
2. Flutter中如何處理來自外部應用的Intent
?
Flutter
可以通過直接訪問Android layer
來處理來自Android
的Intent
,或者請求共享資料。
在以下示例中,會註冊一個文字共享的Intent
過濾器到執行我們Flutter
程式碼的本地Activity
,然後其他應用就能共享文字資料到我們的Flutter
應用。
基本流程就是,我們先在Android
本地層(Activity
)中先處理這些共享資料,然後等待Flutter
請求,而當Flutter
請求時,就可以通過MethodChannel
來將資料提供給它了。
首先,在AndroidManifest.xml
中註冊Intent
過濾器:
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- ... -->
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
複製程式碼
接著在MainActivity
中處理Intent
,提取通過Intent
共享的資料,然後先存放起來,當Flutter
準備好處理時,它會通過平臺通道(platform channel
)進行請求,接著從本地將資料傳送給它就行了。
package com.example.shared;
import android.content.Intent;
import android.os.Bundle;
import java.nio.ByteBuffer;
import io.flutter.app.FlutterActivity;
import io.flutter.plugin.common.ActivityLifecycleListener;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugins.GeneratedPluginRegistrant;
public class MainActivity extends FlutterActivity {
private String sharedText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
Intent intent = getIntent();
String action = intent.getAction();
String type = intent.getType();
if (Intent.ACTION_SEND.equals(action) && type != null) {
if ("text/plain".equals(type)) {
handleSendText(intent); // Handle text being sent
}
}
MethodChannel(getFlutterView(), "app.channel.shared.data")
.setMethodCallHandler(MethodChannel.MethodCallHandler() {
@Override
public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
if (methodCall.method.contentEquals("getSharedText")) {
result.success(sharedText);
sharedText = null;
}
}
});
}
void handleSendText(Intent intent) {
sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
}
}
複製程式碼
最後,當Flutter
的控制元件渲染完成時請求資料:
import 'package:flutter/material.dart';
import 'package:flutter/services.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 Shared App Handler',
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> {
static const platform = const MethodChannel('app.channel.shared.data');
String dataShared = "No data";
@override
void initState() {
super.initState();
getSharedText();
}
@override
Widget build(BuildContext context) {
return Scaffold(body: Center(child: Text(dataShared)));
}
getSharedText() async {
var sharedData = await platform.invokeMethod("getSharedText");
if (sharedData != null) {
setState(() {
dataShared = sharedData;
});
}
}
}
複製程式碼
3. 對應於startActivityForResult
的是啥?
在Flutter
中,Navigator
用於處理Rote
,也被用於獲取已壓棧Route
的返回結果,等push()
返回的Future
執行結束就能拿到結果了:
Map coordinates = await Navigator.of(context).pushNamed('/location');
複製程式碼
然後,在定位功能的Route
中,當使用者選擇完位置資訊後,就可以通過pop()
將結果一同返回了:
Navigator.of(context).pop({"lat":43.821757,"long":-79.226392});
複製程式碼
三、非同步 UI
1. 在Flutter
中與runOnUiThread()
相對應是什麼?
Dart
有個單執行緒執行模型,支援Isolate
(一種在其他執行緒執行Dart
程式碼的方式)、事件迴圈(event loop
)以及非同步程式設計。除非你自己建立一個Isolate
,否則你的Dart
程式碼都會執行在主UI
執行緒,並且由事件迴圈驅動。Flutter
中的事件迴圈跟Android
主執行緒的Looper
是等同的,也就是說,Looper
都繫結在UI
執行緒。
Dart
擁有單執行緒執行模型,但是並不意味著你需要通過這種阻塞式的操作方式執行所有程式碼,這會造成UI
被凍結(freeze
)。不像Android
,需要你在任意時刻都保持主執行緒無阻塞,在Flutter
中,可以使用Dart
語言提供的非同步特性,如async/await
來執行非同步任務。
如下示例,你可以使用Dart
的async/await
來處理網路請求程式碼,而不在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
,接著觸發控制元件子樹的重建並更新資料。如下示例描述瞭如何非同步載入資料,然後填充到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
和Android
在這一問題上的區別,將在下面描述。
2. 如何將工作任務轉移到後臺執行緒?
在Android
中,如果你想要訪問網路資料,那麼你需要切換到後臺執行緒中執行,以避免阻塞主執行緒而導致ANR
,比如,你會使用Asynctask
、LiveData
、IntentService
、JobScheduler
或者RxJava Scheduler
進行後臺執行緒處理。
因為Flutter
是個單執行緒模型,並且執行著事件迴圈(event loop
,如Node.js
),因此不需要擔心執行緒管理或派生執行緒。如果你需要進行I/O
操作,如磁碟訪問或網路請求,那麼可以通過使用async/await
安全的執行所有操作,另外,如果你需要進行會使CPU
保持繁忙的密集型計算操作,那麼你需要轉移到Isolate
(隔離區),以避免阻塞事件迴圈,就跟避免在Android
的主執行緒中進行任何耗時操作一樣。
對於I/O
操作,將函式定義成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
操作。
在Android
中,如果你繼承AsyncTask
,那麼通常你需要複寫三個方法,onPreExecute()
、doInBackground()
、onPostExecute()
,而在Flutter
中則沒有與之對應的方式,因為await
修飾的耗時任務函式,剩餘的工作都交給Dart
的事件迴圈去處理了。
然而當你處理大量資料時,你的UI
會掛提(hangs
),因此在Flutter
中需要使用Isolate
來充分利用CPU
的多核心優勢,以進行耗時任務,或者運算密集型任務。
Isolate
是分離的執行執行緒,它不會與主執行執行緒共享記憶體堆,這就意味著你不能在Isolate
中直接訪問主執行緒的變數,或者呼叫setState
更新UI
。不像Android
中的執行緒,Isolate
如其名,不能共享記憶體(比如不能以靜態欄位的方式共享等)。
下面示例描述瞭如何從一個簡單的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 {
@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;
}
}
複製程式碼
3. Flutter
中對應於OkHttp
的是啥?
在Flutter
中,可以使用http包進行網路請求。
在http
包中沒有任何與OkHttp
相對應的特性,它對我們通常自己實現網路請求的方式進行了更進一步的抽象,使得網路請求更加簡單。
要使用http
包,需要在pubspec.yaml
中新增如下依賴:
dependencies:
...
http: ^0.11.3+16
複製程式碼
如下建立非同步網路請求:
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);
});
}
}
複製程式碼
4. 如何顯示耗時任務的執行進度?
在Android
中,通常在後臺執行緒中執行耗時任務時,將進度顯示於ProgressBar
,而在Flutter
中則是使用ProgressIndicator
控制元件。通過boolean
標記位來控制何時開始渲染,然後在耗時任務開始之前更新它的狀態,並在任務結束時隱藏掉。
下面示例中,build
函式分割成了三個不同的子函式,如果showLoadingDialog()
返回true
,則渲染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 {
@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);
});
}
}
複製程式碼