给Android开发者的Flutter指南 (上) [翻译]

官方英文原文: flutter.io/flutter-for…java

提示:因为篇幅很长,因此分为上下两篇给Android开发者的Flutter指南 (下)已经翻译完成,感兴趣的同窗能够看看android


1、对应于 View

Flutter中能够将Widget当作View,可是又不能当成是Andriod中的View,能够类比的理解。数据库

View不一样的是,Widget的寿命不一样,它们是不可变的,直到它们须要改变时才会退出,而每当它们的状态发生改变时,Flutter框架都会建立新的Widget实例,相比之下,View只会绘制一次直到下一次调用invalidate编程

FlutterWidget是很轻量的,部分缘由归咎于它们的不可变性,由于它们自己不是View视图,且不会直接绘制任何东西,而是用于描述UIjson

1. 如何更新Widgetcanvas

Android中,能够直接改变view以更新它们,可是在Flutter中,widget是不可变的,不能直接更新,而须要经过Widget state来更新。安全

这就是StatelessWidgetStatefulWidget的来源bash

  • StatelessWidget 一个没有状态信息的Widget。当描述的用户界面部分不依赖于对象中的配置信息时,StatelessWidgets会颇有用。这就相似于在Android中使用ImageVIew显示logo,这个logo并不须要在运行期间作任何改变,对应的在Flutter中就使用StatelessWidget网络

  • StatefulWidget 若是你想要基于网络请求获得的数据动态改变UI,那么就使用StatefulWidget,而且告诉Flutter框架,这个WidgetState(状态)已经发生改变,能够更新Widget了。app

值得注意的是,在Flutter内核中,StatelessWidgetStatefulWidget二者的行为是相同的,它们都会重建每一帧,不一样的是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中,能够调用父容器的addChildremoveChild动态添加和删除子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来建立动画,或者调用viewanimate()方法。而在Flutter中,则是将控件包裹在动画控件内,而后使用动画库执行动画。

Flutter中,使用AnimationController(它是个Animation<double>)能够暂停、定位、中止以及反转动画。它须要一个Ticker用于示意(signal)vsync在什么时候产生,而后在它所运行的帧上产生一个值在[0,1]之间的线性插值,你能够建立一个或多个Animation,而后将他们绑定到控制器上。

比方说,你可能使用一个CurvedAnimation来沿着插值曲线执行动画,这种状况下,控制器就是动画进度的“master”资源,而CurvedAnimation则用于计算、替换控制器默认线性动做的曲线,正如Flutter中的Widget同样,动画也是组合起来工做的。

当构建控件树时,你为控件的动画属性指定了Animation ,比方说FadeTransition的不透明度(opacity),接着就是告诉控制器来执行动画了。如下示例描述了如何编写一个在点击FloatingActionButton时如何将控件淡化(fade)成logoFadeTransition

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 tutorialAnimations overview..

5. 如何使用Canvas画图?

Android中,你会经过CanvasDrawable来绘制图形,Flutter中也有个相似的Canvas API,由于它们都基于底层的渲染引擎Skia,所以在使用FlutterCanvas画图操做对于Android开发者来讲是件很是熟悉的事情。

Flutter中有两个帮助绘图的类:CustomPaintCustomPainter,其中后者用于实现你的绘图逻辑。

学习如何在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呢?组合RaisedButtonText,而不是继承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"),
  );
}
复制代码

2、对应于 Intent

1. Flutter中与Intent相对应的是什么?

Android中,Intent有两个主要用途:用于activity间的跳转、用于组件间的通讯。而在Flutter中,没有Intent这个概念,虽然你依然能够经过本地集成(native integrations(使用插件))来启动Intent

Flutter也没有与activityfragment直接对应的组件,而是使用NavigatorRoute来进行屏幕间的切换,这跟activity相似。

Route是应用屏幕和页面的抽象,而Navigator则是一个管理Route的控件。能够粗略的将Route当作activity,可是它们含义不一样。Navigator经过pushpop(可当作压栈和出栈)Route来切换屏幕,Navigator工做原理可当作一个栈,push表示向前切换,pop表示返回。

Android中,须要在AndroidManifest.xml中声明activity,而在Flutter中,你有如下页面切换选择:

  • 指定一个包含全部Route名字的MapMaterialApp
  • 直接切换到RouteWidgetApp

以下示例为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的名字直接pushNavigator的方式:

Navigator.of(context).pushNamed('/b');
复制代码

另外一个使用Intent的使用场景是调用外部组件,好比相机、文件选择器,对于这种状况,你须要建立一个本地平台的集成(native platform integration),或者使用已有的插件;

关于如何构建本地平台集成,请查看 Developing Packages and Plugins..

2. Flutter中如何处理来自外部应用的Intent

Flutter能够经过直接访问Android layer来处理来自AndroidIntent,或者请求共享数据。

在如下示例中,会注册一个文本共享的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});
复制代码

3、异步 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来执行异步任务。

以下示例,你可使用Dartasync/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);
    });
  }
}
复制代码

关于更多后台线程的信息,以及FlutterAndroid在这一问题上的区别,将在下面描述。

2. 如何将工做任务转移到后台线程?

Android中,若是你想要访问网络数据,那么你须要切换到后台线程中执行,以免阻塞主线程而致使ANR,好比,你会使用AsynctaskLiveDataIntentServiceJobScheduler或者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);
    });
  }
}

复制代码
相关文章
相关标签/搜索