给Android开发者的Flutter指南 (上) [翻译]
官方英文原文: 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);
});
}
}