使用 Flutter 实现跨平台移动端开发

原文地址:[https://www.smashingmagazine.com/2018/06/google-flutter-mobile-development/]

【译者注:链接序号对应下面索引列表,另外可以点击阅读原文查看详细的链接文章】

Flutter 是一款由 Google 开发的开源、跨平台移动端开发框架。它允许使用同一个代码库构建高性能、漂亮的 iOS 和 Android 应用,同时它也是 Google 即将推出的 Fuchsia 操作系统的开发平台。此外,通过自定义的 Flutter 引擎可以将其嵌入到其他平台。

Flutter 为什么会出现?为什么要使用它呢?

一直以来,跨平台工具采用以下两种方法之一:

  • 在原生应用程序中嵌入 web view ,像构建网站一样构建应用程序。
  • 封装原生平台里的控件并为它们提供一些跨平台的参数。

为了使移动端开发变得更好,Flutter 尝试了一种不同的方法。它提供了开发人员工作的框架应用程序和能够托管应用程序的可移植运行时的引擎。该框架依托 Skia 图形库而构建,提供了实际渲染时用到的 widgets,而不仅仅是原生应用控件的包装器。就像 web 包装器选项提供的那样,该方法可以灵活的以完全自定义的方式构建跨平台应用程序,同时还会提供流畅的性能体验。与此同时,Flutter 自带的丰富的 widget 库以及一些开源的 widgets 使其成为一个功能丰富的平台。简言之,Flutter 目前是移动端开发者接触到的最接近跨平台开发的东西。

Dart

Flutter 应用程序使用 Dart 编写,Dart 是最初由 Google 开发的一种编程语言。它是一种支持预编译和实时编译的面向对象语言,所以比较适合开发原生应用程序,配合 Flutter 的热加载可以提供高效的开发工作流程。Flutter 最近也转向使用 Dart 2.0 版。Dart 语言提供了许多其他编程语言具有的功能,包括垃圾收集、异步等待、强类型、泛型以及丰富的标准库等等。这些功能对于各种编程语言的开发者们来说都比较熟悉,例如 C#、JavaScript、F#、Swift 和 Java。此外,Dart 可以编译为 Javascript,与 Flutter 结合可以在 web 和移动平台实现代码共享。

事件历史时间表

  • 2015.04

在 Dart 开发者峰会 [1] 上提出 Flutter(最初命名为 Sky )。

  • 2015.11

Sky 重新命名为 Flutter。

  • 2018.02

在2018年世界移动通信大会上,Flutter beta 1 [2] 版本发布。

  • 2018.04

Flutter beta 2 [3] 版本发布

  • 2018.05

Flutter beta 3 [4] 版本在 Google I/O 上发布。Google 宣布 Flutter 可以用于开发应用程序。

与其他开发平台比较

APPLE/ANDROID NATIVE

原生应用程序在使用新功能时带来的困扰是最少的。由于应用程序是使用平台供应商自己(Apple 或 Google)的控件构建,为了让用户体验更加符合给定的平台,因此他们通常遵循这些供应商制定的设计指南。大多数情况下,原生的应用将会比那些跨平台构建的应用性能要好一些,尽管在很多情况下两者的差异可以忽略不计,不过具体还要取决于底层跨平台技术。原生应用的一大优势是:当需要时,他们可以立即采用 Apple 和 Google 在测试版中开发的新技术而不用等待第三方的集成。构建原生应用的主要缺点是缺乏跨平台的代码复用,如果同时开发 iOS 和 Android 应用,那么开发成本可能会很高。

REACT NATIVE

React Native 允许原生应用使用 JavaScript 构建。应用中用到的控件实际上都是原生平台里的控件,所以用户使用起来感觉和原生应用一样。对于那些 React Native 没有提供的需要自定义的应用,仍然需要使用原生开发。当需要定制的模块比较多时,某些情况下,在 React Native 中开发不如使用原生开发更合适。

XAMARIN

当谈到 Xamarin 时,有两种不同的方法将会被提及。跨平台方法:Xamarin.Forms。该方法不同于 React Native,但是从概念上讲是相似的,因为它也是抽象原生控件。同样的,在定制方面它也有和 React Native 同样的缺点。第二种方法:Xamarin-classic。该方法分开使用 Xamarin 的 iOS 和 Android 产品来构建适用于特定平台的功能,就像直接使用 Apple/Android 原生功能一样,只不过在 Xamarin 中需要使用 C# 或 F# 。使用 Xamarin 的好处是可以共享非平台特定的代码,例如网络、数据访问、Web 服务等。

与上面的替代方法不同,Flutter 试图给开发者一个更加完整的跨平台解决方案,包括代码复用、高性能、流畅的用户界面和出色的工具。

一个 Flutter 应用概述

创建一个应用程序

安装 Flutter [5] 之后,使用 Flutter 创建应用程序则非常简单:打开命令行,输入 flutter create[app_name], 在 VS Code 中选择 Flutter:NewProject;在 Android Studio 或 IntelliJ 中选择 StartanewFlutterProject。无论你是选择使用 IDE 还是使用首选编辑器里的命令行,新的 Flutter 应用程序模板都为你提供了一个良好的应用起点。

该应用程序引入了 flutter/material.dart 包,它为应用程序提供了一些基本的元素,例如标题栏、icons 和主题。它还设置了一个带有状态的 widget 来演示当应用程序里的 state 发生变化时用户界面是如何更新的。下面是 Flutter 应用运行在 iOS 和 Android 上的图片:

工具选项

Flutter 在工具方面提供了令人难以置信的灵活性。就像可以从支持的 IDE( 比如 VSCode、 AndroidStudio、或 IntelliJ )中进行开发一样,应用程序可以简单的在任何编辑器的命令行中进行开发。使用何种开发工具很大程度上取决于开发者的喜好。 AndroidStudio提供了大部分的功能,比如用于分析正在运行应用的 widgets 以及监控应用程序性能的 Flutter 检查器。它还提供了一些重构模板,在开发带有层次结构的 widget 时用起来将会很方便; VSCode 提供了更加轻快的开发体验,因为它启动的速度比 AndroidStudio 或 IntelliJ 都要快,而且每个 IDE 都内置了编辑助手,例如代码补全、各种 API 处理以及良好的调试支持;命令行也很好的支持了 Flutter 命令,这使得创建、更新和发布应用都变得简单 ,除了编辑器外不再依赖于其他工具。以下是在各种环境中使用 Flutter 的情景:

热加载

无论使用哪种工具,Flutter 都可以很好的支持热加载。这样在许多情况下就可以修改正在运行的应用程序、维护其状态,而不必停止运行、重新构建和部署了。通过快速迭代,热加载可以极大的提升开发效率。这样也使得这个平台使用起来更友好。

测试

Flutter 包含 WidgetTester 实用程序,用于和测试中的 widgets 交互。新的应用程序模板包含一个示例测试,用来演示在编写测试时如何使用它,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Test included with the new Flutter application template
 
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
 
import 'package:myapp/main.dart';
 
void main() {
  testWidgets('Counter increments smoke test', (WidgetTester tester) async {
    // Build our app and trigger a frame.
    await tester.pumpWidget(new MyApp());
 
    // Verify that our counter starts at 0.
    expect(find.text('0'), findsOneWidget);
    expect(find.text('1'), findsNothing);
 
    // Tap the '+' icon and trigger a frame.
    await tester.tap(find.byIcon(Icons.add));
    await tester.pump();
 
    // Verify that our counter has incremented.
    expect(find.text('0'), findsNothing);
    expect(find.text('1'), findsOneWidget);
  });
}

包和插件的使用

虽然 Flutter 刚宣布可以使用,但已经有一个丰富的开发者生态系统可以使用:A plethora of packages and plugins [6]。当添加一个包或者插件时,只需要在应用程序根目录下的 pubspec.yaml 文件中添加依赖即可。然后通过命令行或 IDE 运行 flutter packagesget, Flutter 就会引入所需的全部依赖。例如,在 Flutter 中使用比较流行的 image picker 插件,则只需要在 pubspec.yaml 文件中添加依赖,如下所示:

1
2
dependencies:
image_picker: "^0.4.1"

接着运行 flutter packagesget 命令就会引入使用时所需的一切东西,然后在 Dart 中导入和使用:

1
import 'package:image_picker/image_picker.dart';

Widgets

在 Flutter 中一切皆 widget 。widget 包括用户界面元素,例如 ListViewTextBox 和 Image以及框架的其他部分,例如布局、动画、手势识别和主题等等。widget 化的设计使得整个应用程序也可以嵌入带有层次结构的其他 widget 中(整个应用程序也是一个 widget)。widget 化的体系结构可以清楚的追踪应用程序中一部分的属性和行为,这与其他大部分应用程序框架不同,大多数的框架是将属性和行为不一致的关联起来,有时将它们与层次结构中的其他组件相关联,有时将其与控件本身相关联。

简单的 UI WIDGET 示例

一个 Flutter 应用的入口是其主要功能。如下所示,在用户界面元素中放入一个 widget,在函数 main() 中调用 runApp() 。

1
2
3
4
5
6
7
import 'package:flutter/material.dart';
 
void main() {
runApp(
Container(color: Colors.lightBlue)
);
}

结果是一个淡蓝色的 Container widget 铺满了屏幕。

  

无状态和有状态 widgets

widgets 分为两种:无状态的 widgets 和有状态的 widgets 。无状态的 widgets 在他们创建和初始化之后内容不再改变,而有状态的 widgets 当应用程序在运行中时允许改变某些状态,例如与用户之间的交互。举个例子,在应用中同时引用了 FlatButtonwidget 和 Textwidget。 Textwidget 的 state 设置了默认的 String。当按下按钮时导致 state 值改变,这会引起 Textwidget 更新从而显示一个新的 String 值。如果要对 widget 封装则需要创建一个派生自 StatelessWidget 或 StatefulWidget的类。例如,淡蓝色的 Container 可像下面这样写:

1
2
3
4
5
6
class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(color: Colors.lightBlue);
  }
}

当创建的 widget 插入到 widget 树中后,Flutter 将会调用 widget 的 build 方法,所以 UI 会被渲染。对于一个有状态的 widget 应该是派生自 StatefulWidget 类:

1
2
3
4
5
6
7
8
9
class MyStatefulWidget extends StatefulWidget {
 
  MyStatefulWidget();
 
  @override
  State createState() {
    return MyWidgetState();
  }
}

有状态的 widget 将会返回一个 State 类,该类负责为给定的 state 构建 widget 树。当 state 改变时,相应的 widget 树将会重新构建。在下面的代码中,当按钮被点击时,State 类将会更新 String 的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class MyWidgetState extends State {
  String text = "some text";
 
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.lightBlue,
      child: Padding(
        padding: const EdgeInsets.all(50.0),
        child: Directionality(
          textDirection: TextDirection.ltr,
          child: Column(
            children: [
              FlatButton(
                child: Text('Set State'),
                onPressed: () {
                  setState(() {
                    text = "some new text";
                  });
                },
              ),
               Text(
                text,
                style: TextStyle(fontSize: 20.0)),
            ],
          )
        )
      )
    );
  }
}

通过 setState() 可以更新 state 值。当 setState() 被调用时,这个函数可以重置任何内部的 state 值,像上述例子中的 String;然后调用 build 方法,更新状态 widget 树。

  

还要注意可以使用 Directionality widget 为其子树中需要它的 widget 设置文本方向,比如 Text widgets。这里的示例是从头开始构建代码,所以在 widget 层次结构的一些地方是需要使用 Directionality。然而,使用 MaterialApp widget 会隐式设置文本方向(例如使用默认应用程序模板)。

布局

默认情况下,runApp 函数会将 widget 放大至铺满整个屏幕。为了控制 widget 的布局,Flutter 提供了很多布局 widgets。这些 widgets 允许垂直或水平对齐子 widgets、将 widget 放大以铺满某个特定区域、将 widget 限制在某个区域中、将其置于屏幕中心以及允许 widgets 之间相互重叠。比较常用的两个 widgets 是 Row 和 Column。它们可以水平(Row)或者垂直(Column)显示其子 widgets。使用这些布局 widgets 只需将它们包装在子 widgets 的列表中, mainAxisAlignment 会将 widgets 控制在布局轴的位置,不论是居中、开始、结束还是使用各种间距选项。下面的代码展示了怎样在 Row 或者 Column 中对齐子 widgets。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyStatelessWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Row( //change to Column for vertical layout
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Icon(Icons.android, size: 30.0),
        Icon(Icons.pets, size: 10.0),
        Icon(Icons.stars, size: 75.0),
        Icon(Icons.rowing, size: 25.0),
      ],
    );
  }
}

  

响应触摸

触摸交互是由手势处理的,手势是封装在 GestureDetector 类中。由于它也是个 widget,添加手势识别和在 GestureDetector 中封装子 widgets 一样简单。例如,在一个 Icon 上添加触摸事件,将其封装在 GestureDetector中并设置处理程序以捕获所需的手势。

1
2
3
4
5
6
7
8
9
10
11
class MyStatelessWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () => print('you tapped the star'),
      onDoubleTap: () => print('you double tapped the star'),
      onLongPress: () => print('you long pressed the star'),
      child: Icon(Icons.stars, size: 200.0),
    );
  }
}

在这种情况下,当轻击,双击或长按图标时,将打印相关文本:

1
2
3
4
5
6
To hot reload your app on the fly, press "r". To restart the app entirely, press "R".
An Observatory debugger and profiler on iPhone X is available at: http://127.0.0.1:8100/
For a more detailed help message, press "h". To quit, press "q".
flutter: you tapped the star
flutter: you double tapped the star
flutter: you long pressed the star

除了简单的点击手势外,还有很多丰富的识别功能,适用于从平移、缩放及拖动的所有内容,这也使得构建带有交互的应用程序变得简单。

绘画

Flutter 还提供了很多绘画相关的 widgets,包括修改不透明度、设置剪切路径和应用设置。通过使用 CustomPaintwidget、 CustomPainter 和 Canvas 类的结合,它还支持普通的绘画功能。绘画 widget 的一个示例是 DecoratedBox,可以在屏幕中画一个 BoxDecoration。下面的例子说明了如何使用 DecoratedBox 和渐变填充填充屏幕。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyStatelessWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new DecoratedBox(
      child: Icon(Icons.stars, size: 200.0),
      decoration: new BoxDecoration(
        gradient: LinearGradient(
          begin: Alignment.topCenter,
          end: Alignment.bottomCenter,
          colors: [Colors.red, Colors.blue, Colors.green],
          tileMode: TileMode.mirror
        ),
      ),
    );
  }
}

  

动画

Flutter 包含一个 AnimationController 类,它可以控制一段时间内的动画播放,包括启动和停止动画以及将值变为动画。此外,有一个 AnimatedBuilder 的 widget 允许和 AnimationController 一起使用构建动画。任何的 widget 都包含它的动画属性,像前面展示的装饰星星。例如,将代码重构为一个 StatefulWidget,因为动画也是 state 变化并且将 AnimationController 传递给 State 类允许在构建 widget 时使用动画值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class StarWidget extends StatefulWidget {
  @override
  State createState() {
    return StarState();
  }
}
 
class StarState extends State with SingleTickerProviderStateMixin {
  AnimationController _ac;
  final double _starSize = 300.0;
 
   @override
  void initState() {
    super.initState();
 
    _ac = new AnimationController(
      duration: Duration(milliseconds: 750),
      vsync: this,
    );
    _ac.forward();
  }
 
  @override
  Widget build(BuildContext context) {
 
    return new AnimatedBuilder(
      animation: _ac,
      builder: (BuildContext context, Widget child) {
        return DecoratedBox(
          child: Icon(Icons.stars, size: _ac.value * _starSize),
          decoration: BoxDecoration(
            gradient: LinearGradient(
              begin: Alignment.topCenter,
              end: Alignment.bottomCenter,
              colors: [Colors.red, Colors.blue, Colors.green],
              tileMode: TileMode.mirror
            ),
          ),
        );
      }
   );
  }
}

在这种情况下,该值可以用于改变此 widget 的大小。只要动画值发生变化就会调用构建器函数,从而导致当装饰星星的大小变化超过750毫秒时产生了规模效应。

  

使用原生的功能

平台通道

为了给 Android 和 iOS 上的原生平台 APIs 提供支持,Flutter 应用可以使用平台通道。这将允许 Flutter Dart 代码向托管的 iOS 或 Android 应用程序发送消息。许多可用的开源插件都是使用平台通道上的消息传递构建的。学习如何使用平台通道,Flutter 文档 [7] 包含了一个访问原生电池 APIs 的好文档。

总结

即使是 beta 版本,Flutter 也提供了一个很好的构建跨平台应用程序的解决方案。凭借其出色的工具和热加载,给用户带来了非常好的开发体验;丰富的开源软件包和详细的文档让你可以轻松入门。接下来,除了 iOS 和 Android,Flutter 的开发者将目标指向了 Fuchsia。考虑到 Flutter 引擎架构的可扩展性,看到它应用在其他各种平台上也不会让我感到惊讶。随着社区的发展,现在正是加入 Flutter 的好时机。

扩展阅读

索引列表

[1] https://www.youtube.com/watch?v=PnIWl33YMwA&feature=youtu.be

[2] https://medium.com/flutter-io/announcing-flutter-beta-1-build-beautiful-native-apps-dc142aea74c0

[3] https://medium.com/flutter-io/https-medium-com-flutter-io-announcing-flutters-beta-2-c85ba1557d5e

[4] https://developers.googleblog.com/2018/05/ready-for-production-apps-flutter-beta-3.html

[5] https://flutter.io/get-started/install/

[6] https://pub.dartlang.org/flutter

[7] https://flutter.io/platform-channels/

posted @ 2020-04-03 13:39  小y  阅读(1927)  评论(0编辑  收藏  举报