官方英文原文: flutter.io/flutter-for…java
提示:因爲篇幅很長,因此分爲上下兩篇,給Android開發者的Flutter指南 (下)已經翻譯完成,感興趣的同窗能夠看看android
Flutter
中能夠將Widget
當作View
,可是又不能當成是Andriod
中的View
,能夠類比的理解。數據庫
與View
不一樣的是,Widget
的壽命不一樣,它們是不可變的,直到它們須要改變時纔會退出,而每當它們的狀態發生改變時,Flutter
框架都會建立新的Widget
實例,相比之下,View
只會繪製一次直到下一次調用invalidate
。編程
Flutter
中Widget
是很輕量的,部分緣由歸咎於它們的不可變性,由於它們自己不是View
視圖,且不會直接繪製任何東西,而是用於描述UI
json
1. 如何更新Widget
canvas
在Android
中,能夠直接改變view
以更新它們,可是在Flutter
中,widget
是不可變的,不能直接更新,而須要經過Widget state
來更新。安全
這就是StatelessWidget
和StatefulWidget
的來源bash
StatelessWidget
一個沒有狀態信息的Widget
。當描述的用戶界面部分不依賴於對象中的配置信息時,StatelessWidgets
會頗有用。這就相似於在Android
中使用ImageVIew
顯示logo
,這個logo
並不須要在運行期間作任何改變,對應的在Flutter
中就使用StatelessWidget
。網絡
StatefulWidget
若是你想要基於網絡請求獲得的數據動態改變UI
,那麼就使用StatefulWidget
,而且告訴Flutter
框架,這個Widget
的State
(狀態)已經發生改變,能夠更新Widget
了。app
值得注意的是,在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"),
);
}
複製代碼
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});
複製代碼
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);
});
}
}
複製代碼