给 Xamarin.Forms 开发者的 Flutter 指南(上)

本文档旨在帮助 Xamarin.Forms 开发者利用已有的知识去构建 Flutter 移动应用。如果你懂得 Xamarin.Forms 框架的基本原理,那么你就可以将本文档当作你开始 Flutter 开发的不错的起点。

你的 Android 和 iOS 知识以及技能组合在构建 Flutter 时都是有价值的,因为 Flutter 依赖的原生系统配置都与你配置 Xamarin.Forms 原生项目时一样。Flutter 框架与你创建一个单独的界面时也是一样的,这在多个平台中同样适用。

本文档可用做可指导手册来翻查与你需求最为相关的问题。

本文结构如下:

1. 项目设置(上)

2. 视图(上)

3. 导航(上)

4. 异步 UI(上)

5. 项目结构与资源(下)

6. 应用程序生命周期(下)

7. 布局(下)

8. 手势检测和触摸事件处理(下)

9. 列表视图和适配器(下)

10. 文本处理(下)

11. 表单录入(下)

12. Flutter 插件(下)

13. 主题 (样式)(下)

14. 数据库与本地存储(下)

15. 通知(下)


一、项目设置

1.1 app 是如何运行的项目设置?

对于 Xamarin.Forms 里的每个平台,你可以调用 LoadApplication 方法,创建一个新应用并运行你的 app 。

LoadApplication(new App());

在 Flutter 中,加载 Flutter app 的默认主入口点是 main

void main() {
  runApp(new MyApp());
}

在 Xamarin.Forms 中,你分配一个 Page 到 Application 类中的 MainPage 属性。

public class App: Application
{
    public App()
    {
      MainPage = new ContentPage()
                 {
                   new Label()
                   {
                     Text="Hello World",
                     HorizontalOptions = LayoutOptions.Center,
                     VerticalOptions = LayoutOptions.Center
                   }
                 };
    }
}

在 Flutter 中,“万物皆 widget”,甚至连应用本身也是。接下来的示例展示了 MyApp ,一个简单的应用 Widget

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new Center(
        child: Text("Hello World!", textDirection: TextDirection.ltr));
  }
}

1.2 如何创建一个页面?

Xamarin.Forms 拥有一些不同类型的页面;ContentPage 是最为通用的。

在 Flutter 中,指定一个应用程序 widget 来控制你的根页面。你可以使用一个 MaterialApp widget,他支持Material Design,或者你也可以使用等级较低的 WidgetsApp,可供你随心所欲地定制。

接下来的代码定义了一个主页,一个有状态的 widget。在 Flutter 中,除了以下两个类型的 widget 外,其它 widget 都是不可变的:有状态和无状态 widget。无状态 widget 的示例都是标题、图标或图片。

下面的示例使用 MaterialApp,它在 home 属性中控制它的根页面。

class MyApp extends StatelessWidget {
  // This widget is the root of your application.

  // 这个 widget 是你的应用程序的根 widget。

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

从这里开始,真正的首页是另一个你在里面创建了状态的 widget

一个有状态 widget,例如下面的 MyHomePage,包含两个部分。第一部分,是它自身不变的,创建一个状态对象(State object)来管控对象的状态。状态对象持续存在于 widget 的整个生命周期中。

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

状态对象实现了有状态 widget 中的构建方法。

当 widget 树的状态发生改变,将调用 setState() 触发 widget 当中该部分UI的构建。确保只在需要时调用 setState() ,并且在只有部分 widget 树发生变化时调用,否则会造成糟糕的UI性能表现。

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        // Take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set the appbar title.
        title: new Text(widget.title),
      ),
      body: new Center(
        // Center is a layout widget. It takes a single child and positions it
        // in the middle of the parent.
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Text(
              'You have pushed the button this many times:',
            ),
            new Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: new Icon(Icons.add),
      ),
    );
  }
}

在 Flutter 中的UI(也就是这里所说的 widget 树)是不可变的,意思是说它一旦被构建,你就无法再改变他的状态。当你修改状态类中的字段,就要再次调用 setState 来重新构建整个 widget 树。

这个生成UI的方式不同于 Xamarin.Forms,但是这种方法却有很多益处。

二、视图

2.1 在Flutter 中 页面(Page) 与 元素(Element)的相同点是什么?

类react的风格或者说是_声明式_编程风格与传统的命令式编程风格有何不同?作为比较,可以参考声明式 UI 介绍.

ContentPageTabbedPageMasterDetailPage 就是你可以在 Xamarin.Forms 应用程序中使用的全部页面类型。这些页面会控制元素(Element)来显示各种控件。在 Xamarin.Forms 中,Entry 或者 Button 就是一个 元素 的示例。

在 Flutter 中,几乎所有东西都是一个 widget 。一个页面在 Flutter 中被称作路由(Route),也是一个 widget。按钮、进度条、动画控制器都是 widget 。当构建一个路由时,就会创建一棵 widget 树。

Flutter 包含 Material 组件库。这些都是实现了 Material Design 指南的 widget。Material Design 是一个灵活的针对所有平台的设计系统,包括 iOS。

不过, Flutter 有足够灵活和自描述性(expressive)去实现任何设计语言。举个例子,在 iOS 上,你可以用 Cupertino widget来生成一个看起来像苹果 iOS 设计语言的接口。

2.2 如何更新 widget?

在 Xamarin.Forms 中,每一个页面或者元素都是一个有状态的类, 拥有一些属性和方法。通过更新一个属性来更新你的元素,而且这会传递到原生控件。

在 Flutter 中,Widget 是不可变的,你不可以直接地通过修改一个属性来更新它们,而是应该使用 widget 的状态。

有状态 widget 和无状态 widget 的概念就是出自这里。

无状态 widget(StatelessWidget)顾名思义,就是一个没有状态信息的 widget。

当你在描绘用户界面的一个不依赖除对象中的配置信息之外任何东西的部分时,StatelessWidgets 是有用的。

举个例子,在 Xamarin.Forms 中,可以轻而易举地用你的logo替换一张图片。这个logo将不会在运行过程中修改,所以在 Flutter 会使用StatelessWidget

如果你想动态地基于进行了HTTP调用或者用户交互后接收到的数据来修改UI,你需要使用StatefulWidget并告诉 Flutter 框架这个 widget 的状态(State)已经被更新了所以它可以更新那个 widget。

这里要记下的重要内容是有状态和无状态 widget 的核心行为都是一样的。他们重建每个结构,不同的是 StatefulWidget 拥有一个状态(State)对象来跨结构储存状态数据和恢复它。

如果你有疑惑,那么就记住这个规则:如果一个 widget 改变了(例如是因为用户交互),它就是有状态的。相反,如果一个 widget 对修改作出反应,包含它的父 widget 如果本身没有对修改作出反应,仍然可以是无状态的。

接下来的示例展示了如何使用一个StatelessWidget。一个公共的 StatelessWidgetText widget。如果你查阅文本的实现,你会发现他是 StatelessWidget 的子类。

new Text(
  'I like Flutter!',
  style: new TextStyle(fontWeight: FontWeight.bold),
);

如你所见,文本 widget 没有状态信息与它关联,它只渲染在它的构造函数中呈现的内容,没有更多。

但是,如果你想动态地作出 “I Like Flutter”的修改呢?例如在点击一个FloatingActionButton时。

为了实现这个目标,需要将 Text widget 封装到一个StatefulWidget中,并在用用户点击按钮时更新它, 正如接下来的例子:

import 'package:flutter/material.dart';

void main() {
  runApp(new SampleApp());
}

class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Sample App',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

  @override
  _SampleAppPageState createState() => new _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 new Scaffold(
      appBar: new AppBar(
        title: new Text("Sample App"),
      ),
      body: new Center(child: new Text(textToShow)),
      floatingActionButton: new FloatingActionButton(
        onPressed: _updateText,
        tooltip: 'Update Text',
        child: new Icon(Icons.update),
      ),
    );
  }
}

2.3 该如何布局我的 widget 呢?什么东西可以等价于一个 XAML 文件?

在 Xamarin.Forms 中,大部分开发者用 XAML 写布局,尽管有时用 C#。在 Flutter 中编码一棵 widget 树来编写布局。

接下来的示例展示如何显示一个简单的带填充(padding)的 widget:

@override
Widget build(BuildContext context) {
  return new Scaffold(
    appBar: new AppBar(
      title: new Text("Sample App"),
    ),
    body: new Center(
      child: new MaterialButton(
        onPressed: () {},
        child: new Text('Hello'),
        padding: new EdgeInsets.only(left: 10.0, right: 10.0),
      ),
    ),
  );
}

您可以查看 Flutter 在widget 目录中提供的布局。

2.4 如何从布局中添加或移除一个元素?

在 Xamarin.Forms 中,你需要在代码中移除或添加一个 元素(Element)。如果它说一个列表,这将会涉及设置 Content 属性或者调用 Add() 或者 Remove() 方法。

在 Flutter 中,因为 widget 都是不可变的,所以没有直接对等的东西。相反,你可以将一个返回一个 widget 的函数传递给父级,并用布尔标控制它的子 widget 的创建。

下面的示例展示当用户点击 FloatingActionButton 时,如何在两个 widget 之间切换。

class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Sample App',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

  @override
  _SampleAppPageState createState() => new _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  // Default value for toggle
  bool toggle = true;
  void _toggle() {
    setState(() {
      toggle = !toggle;
    });
  }

  _getToggleChild() {
    if (toggle) {
      return new Text('Toggle One');
    } else {
      return new CupertinoButton(
        onPressed: () {},
        child: new Text('Toggle Two'),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("Sample App"),
      ),
      body: new Center(
        child: _getToggleChild(),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: _toggle,
        tooltip: 'Update Text',
        child: new Icon(Icons.update),
      ),
    );
  }
}

2.5 如何让一个 widget 动起来?

在 Xamarin.Forms 中,你可以利用包括例如 FadeTo 和 TranslateTo 等方法的视图扩展(ViewExtensions)来创建简单的动画。你会在一个视图中使用这些方法来执行需要的动画。

<Image Source="{Binding MyImage}" x:Name="myImage" />

然后再后面的代码或一个动作中,这个会在1秒内淡入这张图像。

myImage.FadeTo(0, 1000);

在 Flutter 中,通过封装 widget 到一个动画 widget 中,可以使用动画类库来让 widget 动起来。使用一个 AnimationController,即一个可以暂停、寻找、停止和倒退动画的 Animation<double> 。它需要一个滴答器(Ticker),当垂直同步(vsync)发生时,会发出信号,并在运行时的每一帧都会产生0和1之间的线性插值。然后你可以创建一个或多个动画并把它们附加到控制器上。

举个例子,你可以使用 CurvedAnimation 来实现一个沿着插值曲线的动画。在这个场景中,控制器说一个动画进展的“大师”源,而 CurvedAnimation 计算用来替代控制器默认线性运动的曲线。跟 widget 一样,Flutter 中的动画与组成一起工作。

当你在构建一个 widget 树,赋值一个动画(Animation)给一个 widget 的一个动画属性时,比如 渐退(FadeTransition)的 不透明度,会告诉控制器开始执行动画。

下面的实例展示如何去写一个 渐退(FadeTransition),当你按下 FloatingActionButton 时,它会把 widget 渐变到一个logo。

import 'package:flutter/material.dart';

void main() {
  runApp(new FadeAppTest());
}

class FadeAppTest extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Fade Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyFadeTest(title: 'Fade Demo'),
    );
  }
}

class MyFadeTest extends StatefulWidget {
  MyFadeTest({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _MyFadeTest createState() => new _MyFadeTest();
}

class _MyFadeTest extends State<MyFadeTest> with TickerProviderStateMixin {
  AnimationController controller;
  CurvedAnimation curve;

  @override
  void initState() {
    controller = new AnimationController(duration: const Duration(milliseconds: 2000), vsync: this);
    curve = new CurvedAnimation(parent: controller, curve: Curves.easeIn);
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new Center(
          child: new Container(
              child: new FadeTransition(
                  opacity: curve,
                  child: new FlutterLogo(
                    size: 100.0,
                  )))),
      floatingActionButton: new FloatingActionButton(
        tooltip: 'Fade',
        child: new Icon(Icons.brush),
        onPressed: () {
          controller.forward();
        },
      ),
    );
  }
}

更多信息,可以查阅 动画 & 运动 widget, 动画教程, 以及动画概述。

2.6 如何在屏幕上绘图?

Xamarin.Forms 从来没有任何内置的方法来直接在屏幕上绘图。如果他们需要一个自定义图像绘制,大多数使用 SkiaSharp。在 Flutter中,你可以直接访问 Skia 画布(Skia Canvas) 方便地在屏幕上绘图。

Flutter 拥有两个类来帮助你在画布上绘图:CustomPaint 和 CustomPainter,后者实现了你在画布上绘图的算法。

如果想学习如何在 Flutter 中实现一个签名画手,请阅读 Collin 在 StackOverflow 的回答。

import 'package:flutter/material.dart';

void main() => runApp(new MaterialApp(home: new DemoApp()));

class DemoApp extends StatelessWidget {
  Widget build(BuildContext context) => new Scaffold(body: new Signature());
}

class Signature extends StatefulWidget {
  SignatureState createState() => new SignatureState();
}

class SignatureState extends State<Signature> {
  List<Offset> _points = <Offset>[];
  Widget build(BuildContext context) {
    return new GestureDetector(
      onPanUpdate: (DragUpdateDetails details) {
        setState(() {
          RenderBox referenceBox = context.findRenderObject();
          Offset localPosition =
          referenceBox.globalToLocal(details.globalPosition);
          _points = new List.from(_points)..add(localPosition);
        });
      },
      onPanEnd: (DragEndDetails details) => _points.add(null),
      child: new CustomPaint(painter: new SignaturePainter(_points), size: Size.infinite),
    );
  }
}

class SignaturePainter extends CustomPainter {
  SignaturePainter(this.points);
  final List<Offset> points;
  void paint(Canvas canvas, Size size) {
    var paint = new 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;
}

2.7 widget 的不透明度在哪里?

Xamarin.Forms 上,所有 虚拟元素(VisualElement)都拥有一个不透明度。在 Flutter 中,你需要封装一个 widget 到一个 不透明度 widget 来实现它。

2.8 如何构建一个自定义 widget ?

在 Xamarin.Forms 中,通常派生 VisualElement 或使用一个已有的 VisualElement ,来重写和实现所需行为的方法。

在 Flutter 中,通过 组合(composing) 更小的 widget(而不是扩展它们)来构建一个自定义 widget。这有点类似于基于 Grid实现自定义控件,其中添加了大量 VisualElement,同时使用自定义逻辑进行扩展。

举个例子,如何构建一个在构造器接受一个标签的自定义按钮?创建一个组合了一个带有标签的RaisedButton的自定义按钮,而不是扩展 RaisedButton 。

class CustomButton extends StatelessWidget {
  final String label;

  CustomButton(this.label);

  @override
  Widget build(BuildContext context) {
    return new RaisedButton(onPressed: () {}, child: new Text(label));
  }
}

然后就可以像使用其他 Flutter widget 一样使用这个自定义按钮

@override
Widget build(BuildContext context) {
  return new Center(
    child: new CustomButton("Hello"),
  );
}

三、导航

3.1 如何在页面之间导航?

在 Xamarin.Forms 中,在页面之间导航通常会通过一个 CarouselPage。在 Flutter 中,你可以使用一个 NavigationPage 来管理页面栈去显示。

Flutter 也有类似的实现,使用了一个导航器(Navigator)路由(Routes)。一个路由是一个应用程序里一个页面的抽象,而一个导航器是一个管理路由的 widget。

一个路由大致上映射到一个页面。导航器以类似 Xamarin.Forms NavigationPage 的方式工作, 在里面可以 push() 和 pop() 路由,依赖于你是否想导航到一个视图,或者从它返回。

在页面间导航,你有几个选择:

  • 指定路由名称的一个映射。(MaterialApp)

  • 直接导航到一个路由。(WidgetApp)

接下来构建一个映射的示例。

void main() {
  runApp(new MaterialApp(
    home: new MyAppHome(), // becomes the route named '/'
    routes: <String, WidgetBuilder> {
      '/a': (BuildContext context) => new MyPage(title: 'page A'),
      '/b': (BuildContext context) => new MyPage(title: 'page B'),
      '/c': (BuildContext context) => new MyPage(title: 'page C'),
    },
  ));
}

通过推入一个路由的名称到导航器来导航到这个路由。

Navigator.of(context).pushNamed('/b');

导航器是一个管理你的应用程序的路由的堆栈。把一个路由推入堆栈可以移动到这个路由, 而从堆栈弹出一个路由可以返回到前一个路由。这是通过等待被 push() 返回的 未来(Future) 来完成的。

Async/await 与 .NET 的实现非常类似,并且是在 Async UI中有更详尽的解释。

举个例子,要开始一个让用户选择他们的定位的 定位(location) 路由,你需要做以下步骤:

 
 

然后,在你的“定位”路由里,一旦用户选择他们的定位,使用结果来 pop() 这个堆栈。

 
 

3.2 如何导航到其它应用程序?

在 Xamarin.Forms 中,需要用指定的 URI 协议并使用 Device.OpenUrl("mailto://") 来传送用户到其它应用程序。

为了在 Flutter 中实现这个功能,需要创建一个原生平台集成,或者使用 已有的插件, 比如 url_launcher,可与在 Pub site 上的许多其他包一起使用。

四、异步 UI

4.1 在 Flutter 中有什么是跟 Device.BeginOnMainThread() 相等的?

Dart 拥有一个单线程执行模型,支持“隔离” (在另一个线程上运行Dart代码的方法)、事件循环和 异步编程。除非生成一个“隔离”,否则您的Dart代码会在主UI线程中运行,并由一个事件循环来驱动。

Dart 的单线程模型并不意味着需要以会导致UI冻结的阻塞操作方式来运行所有内容。更多地像 Xamarin.Forms 一样需要让 UI 线程保持空闲。您将使用“async”/“wait”来执行任务,其中必须等待响应。

在 Flutter 中,使用 Dart 语言提供的异步工具(也称为 async/await)来执行异步工作。这跟 C# 很像,并且对于 Xamarin.Forms 开发者来说应该是非常容易使用的。

例如,您可以使用 async/await 运行网络请求代码,而不会导致UI挂起,并让Dart完成繁重的工作:

loadData() async {
  String dataURL = "https://jsonplaceholder.typicode.com/posts";
  http.Response response = await http.get(dataURL);
  setState(() {
    widgets = json.decode(response.body);
  });
}

一旦完成等待的网络调用后,通过调用 setState() 更新UI,这将触发 widget 子树的重新构建并更新数据。

下面的实例异步加载数据并在一个 ListView 中显示:

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

void main() {
  runApp(new SampleApp());
}

class SampleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Sample App',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

  @override
  _SampleAppPageState createState() => new _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  List widgets = [];

  @override
  void initState() {
    super.initState();

    loadData();
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("Sample App"),
      ),
      body: new ListView.builder(
          itemCount: widgets.length,
          itemBuilder: (BuildContext context, int position) {
            return getRow(position);
          }));
  }

  Widget getRow(int i) {
    return new Padding(
      padding: new EdgeInsets.all(10.0),
      child: new 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 的不同之处的更多信息,请参考下一节。

4.2 如何将工作转移到后台线程?

因为 Flutter 是单线程的,并且运行一个事件循环,所以您不必担心线程管理或产生后台线程。这一点与 Xamarin.Forms 非常相似。如果您正在做 I/O 密集型的工作,比如磁盘访问或网络调用,那么您可以安全地使用 async/await,这样就一切就绪了。

另一方面,如果您需要做计算密集型的工作,使CPU保持忙碌,那么您希望将它移动到“隔离”状态,以避免阻塞事件循环,就像您将任何类型的工作放在主线程之外一样。这类似于通过 Xamarin.Forms 中的 Task.Run() 将内容移动到另一个线程。

对于 I/O 密集型的工作,将函数声明为一个 异步 函数,并在函数内部 等待 长时间运行的任务:

loadData() async {
  String dataURL = "https://jsonplaceholder.typicode.com/posts";
  http.Response response = await http.get(dataURL);
  setState(() {
    widgets = json.decode(response.body);
  });
}

这是您通常执行网络或数据库调用的方式,它们都是I/O操作。

然而,有时您可能正在处理大量数据而UI挂起了。在 Flutter 中,使用隔离来利用多个CPU内核来执行长时间运行或计算密集型任务。

隔离线程是独立的执行线程,不与主执行内存堆共享任何内存。这是与 Task.Run() 的区别。这意味着您不能从主线程访问变量,也不能通过调用 setState() 更新UI。

下面的示例以简单的方式展示了如何将数据共享回主线程以更新UI。

loadData() async {
  ReceivePort receivePort = new 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 = new 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 = new ReceivePort();
  port.send([msg, response.sendPort]);
  return response.first;
}

在这里,dataLoader() 是在它自己单独的执行线程中运行的隔离。在隔离中,您可以执行更多的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(new SampleApp());
}

class SampleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Sample App',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

  @override
  _SampleAppPageState createState() => new _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 new Center(child: new CircularProgressIndicator());
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        appBar: new AppBar(
          title: new Text("Sample App"),
        ),
        body: getBody());
  }

  ListView getListView() => new ListView.builder(
      itemCount: widgets.length,
      itemBuilder: (BuildContext context, int position) {
        return getRow(position);
      });

  Widget getRow(int i) {
    return new Padding(padding: new EdgeInsets.all(10.0), child: new Text("Row ${widgets[i]["title"]}"));
  }

  loadData() async {
    ReceivePort receivePort = new 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 = new 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 = new ReceivePort();
    port.send([msg, response.sendPort]);
    return response.first;
  }
}

4.3 如何发送一个网络请求?

在 Xamarin.Forms 中,你可以使用 HttpClient。当您使用流行的http package包时,在 Flutter 中进行网络调用就很容易了。这将抽象出许多您通常可能自己实现的网络,从而使网络调用变简化。

要使用 http 包,请将它添加到 pubspec.yaml 文件中的依赖项中:

dependencies:
  ...
  http: ^0.11.3+16

需要发送一个网络请求, 在同步函数 http.get()上调用 await:

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.4 如何显示长时间运行的任务的进度?

在 Xamarin.Forms 中常会创建一个加载指示器,可以直接在XAML中创建,也可以通过第三方插件创建,比如 AcrDialogs。

在 Flutter 中,使用一个 加载指示器( ProgressIndicator)widget。通过一个布尔标志控制何时渲染来以编程方式显示进度。告诉 Flutter 在长时间运行的任务开始之前更新它的状态,并在任务结束后隐藏它。

在下面的示例中,build 函数被分成三个不同的函数。如果 showLoadingDialog() 是 true (即当widgets.length == 0时)就会渲染出 进度指示器。另一方面,用网络调用返回的数据渲染 列表视图(ListView)

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

void main() {
  runApp(new SampleApp());
}

class SampleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Sample App',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

  @override
  _SampleAppPageState createState() => new _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 new Center(child: new CircularProgressIndicator());
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        appBar: new AppBar(
          title: new Text("Sample App"),
        ),
        body: getBody());
  }

  ListView getListView() => new ListView.builder(
      itemCount: widgets.length,
      itemBuilder: (BuildContext context, int position) {
        return getRow(position);
      });

  Widget getRow(int i) {
    return new Padding(padding: new EdgeInsets.all(10.0), child: new 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);
    });
  }
}

640?wx_fmt=gif

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值