官方英文原文: https://flutter.io/flutter-for-android/
提示:由于篇幅很长,所以分为上下两篇,给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);
});
}
}