使用 Google 的 Flutter 进行真正的跨平台移动开发

已发表: 2022-03-10
快速总结↬ Flutter 让构建跨平台移动应用程序变得轻而易举。 本文介绍了 Flutter,将其与其他移动开发平台进行了比较,并展示了如何使用它开始构建应用程序。

Flutter 是来自 Google 的开源跨平台移动开发框架。 它允许从单个代码库为 iOS 和 Android 构建高性能、美观的应用程序。 它也是谷歌即将推出的 Fuchsia 操作系统的开发平台。 此外,它的架构方式可以通过自定义 Flutter 引擎嵌入器将其引入其他平台。

为什么创建 Flutter 以及为什么要使用它

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

  • 他们将 Web 视图包装在本机应用程序中,并像构建网站一样构建应用程序。
  • 它们封装了原生平台控件,并在它们之上提供了一些跨平台抽象。

Flutter 采用了不同的方法,试图让移动开发变得更好。 它为开发人员提供了一个框架应用程序,以及一个具有可移植运行时的引擎来托管应用程序。 该框架建立在 Skia 图形库的基础上,提供实际渲染的小部件,而不仅仅是本机控件的包装器。

这种方法提供了以完全自定义的方式构建跨平台应用程序的灵活性,就像 web wrapper 选项提供的那样,但同时提供了流畅的性能。 同时,Flutter 附带的丰富的小部件库,以及大量的开源小部件,使其成为一个功能非常丰富的平台。 简而言之,Flutter 是移动开发人员在跨平台开发方面所拥有的最接近的东西,几乎没有妥协。

跳跃后更多! 继续往下看↓

Flutter 应用程序是用 Dart 编写的,Dart 是一种最初由 Google 开发的编程语言。 Dart 是一种面向对象的语言,支持提前编译和即时编译,非常适合构建原生应用程序,同时通过 Flutter 的热重载提供高效的开发工作流程。 Flutter 最近也迁移到了 Dart 2.0 版。

Dart 语言提供了其他语言中的许多特性,包括垃圾收集、异步等待、强类型、泛型以及丰富的标准库。

Dart 提供了来自各种语言(例如 C#、JavaScript、F#、Swift 和 Java)的开发人员应该熟悉的交叉特性。 此外,Dart 可以编译为 Javascript。 结合 Flutter,这允许跨 Web 和移动平台共享代码。

事件的历史时间表

  • 2015 年 4 月
    在 Dart 开发者峰会上展示的 Flutter(原代号 Sky)
  • 2015 年 11 月
    Sky 重命名为 Flutter
  • 2018 年 2 月
    Flutter beta 1 在 2018 年世界移动通信大会上宣布
  • 2018 年 4 月
    Flutter beta 2 发布
  • 2018 年 5 月
    Flutter beta 3 在 Google I/O 上宣布。 Google 宣布 Flutter 已准备好用于生产应用程序

与其他开发平台的比较

苹果/安卓原生

本机应用程序在采用新功能时提供的摩擦最小。 由于应用程序是使用平台供应商(Apple 或 Google)自己的控件构建的,并且通常遵循这些供应商制定的设计指南,因此他们倾向于使用户体验更符合给定平台。 在大多数情况下,本机应用程序将比使用跨平台产品构建的应用程序执行得更好,尽管在许多情况下差异可以忽略不计,具体取决于底层跨平台技术。

原生应用程序的一大优势是,如果需要,它们可以立即采用 Apple 和 Google 在测试版中创建的全新技术,而无需等待任何第三方集成。 构建本机应用程序的主要缺点是缺乏跨平台的代码重用,如果针对 iOS 和 Android,这可能会使开发成本高昂。

反应原生

React Native 允许使用 JavaScript 构建原生应用程序。 应用程序使用的实际控件是本机平台控件,因此最终用户可以获得本机应用程序的感觉。 对于需要超出 React Native 抽象提供的自定义的应用程序,仍然可能需要原生开发。 在需要大量定制的情况下,在 React Native 的抽象层中工作的好处会减少到在某些情况下本地开发应用程序会更有益的程度。

赛马林

在讨论 Xamarin 时,需要评估两种不同的方法。 对于他们最跨平台的方法,有 Xamarin.Forms。 尽管该技术与 React Native 非常不同,但从概念上讲,它提供了类似的方法,因为它抽象了原生控件。 同样,它在定制方面也有类似的缺点。

其次,有很多术语 Xamarin-classic。 这种方法独立使用 Xamarin 的 iOS 和 Android 产品来构建特定于平台的功能,就像直接使用 Apple/Android 原生时一样,在 Xamarin 情况下仅使用 C# 或 F#。 Xamarin 的好处是可以共享非平台特定代码,例如网络、数据访问、Web 服务等。

与这些替代方案不同,Flutter 试图为开发人员提供更完整的跨平台解决方案,具有代码重用、高性能、流畅的用户界面和出色的工具。

Flutter 应用概述

创建应用程序

安装完 Flutter 后,用 Flutter 创建一个 App 很简单,要么打开命令行输入flutter create [app_name] ,在 VS Code 中选择“Flutter: New Project”命令,或者在 Android 中选择“Start a new Flutter Project”工作室或 IntelliJ。

无论您选择使用 IDE 还是命令行以及您喜欢的编辑器,新的 Flutter 应用程序模板都为您提供了一个良好的应用程序起点。

该应用程序引入了flutter / material.dart包来为应用程序提供一些基本的脚手架,例如标题栏、材质图标和主题。 它还设置了一个有状态的小部件来演示如何在应用程序状态更改时更新用户界面。

新的 Flutter 应用程序模板
在 iOS 和 Android 上运行的新 Flutter 应用程序。 (大预览)

工具选项

Flutter 在工具方面提供了令人难以置信的灵活性。 应用程序可以很容易地从命令行与任何编辑器一起开发,因为它们可以来自受支持的 IDE,如 VS Code、Android Studio 或 IntelliJ。 采取的方法很大程度上取决于开发人员的偏好。

Android Studio 提供了最多的功能,例如 Flutter Inspector 来分析正在运行的应用程序的小部件并监控应用程序的性能。 它还提供了一些重构,在开发小部件层次结构时很方便。

VS Code 提供了更轻松的开发体验,因为它的启动速度往往比 Android Studio/IntelliJ 快。 每个 IDE 都提供内置的编辑助手,例如代码完成,允许探索各种 API 以及良好的调试支持。

命令行也通过flutter命令得到很好的支持,这使得创建、更新和启动应用程序变得容易,除了编辑器之外没有任何其他工具依赖。

颤振工具
Flutter 工具支持多种环境。 (大预览)

热重载

无论使用何种工具,Flutter 都对应用程序的热重载提供了出色的支持。 这允许在许多情况下修改正在运行的应用程序,维护状态,而无需停止应用程序、重建和重新部署。

通过允许更快的迭代,热重载显着提高了开发效率。 它确实使平台成为一种乐趣。

测试

Flutter 包含一个WidgetTester实用程序,用于与测试中的小部件进行交互。 新的应用程序模板包含一个示例测试,以演示如何在编写测试时使用它,如下所示:

 // 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 刚刚开始,但已经有一个丰富的开发者生态系统:大量的包和插件已经可供开发者使用。

要添加包或插件,只需在应用程序根目录的pubspec.yaml文件中包含依赖项即可。 然后从命令行或通过 IDE 运行flutter packages get ,Flutter 的工具将引入所有必需的依赖项。

例如,要使用流行的 Flutter 图像选择器插件, pubspec.yaml只需将其列为依赖项,如下所示:

 dependencies: image_picker: "^0.4.1"

然后运行flutter packages get会带入你使用它所需的一切,之后可以在 Dart 中导入和使用它:

 import 'package:image_picker/image_picker.dart';

小部件

Flutter 中的一切都是一个小部件。 这包括用户界面元素,例如ListViewTextBoxImage ,以及框架的其他部分,包括布局、动画、手势识别和主题,仅举几例。

通过让一切都成为一个小部件,整个应用程序,顺便说一下也是一个小部件,可以在小部件层次结构中表示。 拥有一个一切都是小部件的架构可以清楚地表明应用于应用程序一部分的某些属性和行为来自何处。 这与大多数其他应用程序框架不同,后者不一致地关联属性和行为,有时将它们从层次结构中的其他组件附加到控件本身上。

简单的 UI 小部件示例

Flutter 应用程序的入口点是 main 函数。 要将用户界面元素的小部件放在屏幕上,请在main()中调用runApp()并将作为小部件层次结构根的小部件传递给它。

 import 'package:flutter/material.dart'; void main() { runApp( Container(color: Colors.lightBlue) ); }

这会产生一个填充屏幕的浅蓝色Container小部件:

最小的颤振应用
单个容器的最小化 Flutter 应用程序(大预览)

无状态与有状态小部件

小部件有两种形式:无状态和有状态。 无状态小部件在创建和初始化后不会更改其内容,而有状态小部件维护一些在运行应用程序时可以更改的状态,例如响应用户交互。

在此示例中,将FlatButton小部件和Text小部件绘制到屏幕上。 Text小部件以一些默认String作为其状态开始。 按下按钮会导致状态更改,这将导致Text小部件被更新,显示一个新的String

要封装一个小部件,请创建一个派生自StatelessWidgetStatefulWidget的类。 例如,浅蓝色的Container可以这样写:

 class MyWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Container(color: Colors.lightBlue); } }

Flutter 在将其插入到小部件树时会调用小部件的 build 方法,以便可以渲染这部分 UI。

对于有状态的小部件,从StatefulWidget派生:

 class MyStatefulWidget extends StatefulWidget { MyStatefulWidget(); @override State createState() { return MyWidgetState(); } } class MyStatefulWidget extends StatefulWidget { MyStatefulWidget(); @override State createState() { return MyWidgetState(); } }

有状态的小部件返回一个负责为给定状态构建小部件树的State类。 当状态改变时,widget 树的相关部分被重建。

在以下代码中,当单击按钮时, State类会更新String

 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)), ], ) ) ) ); } } 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()的函数中更新。 调用setState()时,该函数可以设置任何内部状态,例如本例中的字符串。 然后,将调用build方法,更新有状态小部件的树。

状态变化
处理来自用户交互的状态变化(大预览)

另请注意,使用Directionality小部件为其子树中需要它的任何小部件(例如Text小部件)设置文本方向。 这里的示例是从头开始构建代码,因此在小部件层次结构的某个地方需要Directionality 。 但是,使用MaterialApp小部件(例如使用默认应用程序模板)会隐式设置文本方向。

布局

runApp函数默认填充小部件以填充屏幕。 为了控制小部件布局,Flutter 提供了多种布局小部件。 有一些小部件可以执行垂直或水平对齐子小部件的布局,扩展小部件以填充特定空间,将小部件限制在特定区域,在屏幕上居中,并允许小部件相互重叠。

两个常用的小部件是RowColumn 。 这些小部件执行布局以水平(行)或垂直(列)显示其子小部件。

使用这些布局小部件只需将它们包装在一个子小部件列表中。 mainAxisAlignment控制小部件如何沿布局轴定位,居中、在开始、结束或使用各种间距选项。

以下代码显示了如何对齐RowColumn中的多个子小部件:

 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类中的手势来处理的。 由于它也是一个小部件,因此添加手势识别就像将子小部件包装在GestureDetector中一样简单。

例如,要将触摸处理添加到Icon ,使其成为GestureDetector的子项,并设置检测器的处理程序以捕获所需的手势。

 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), ); } }

在这种情况下,当对图标执行点击、双击或长按时,将打印相关的文本:

 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: https://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 还提供了各种用于绘画的小部件,包括修改不透明度、设置剪切路径和应用装饰的小部件。 它甚至支持通过CustomPaint小部件以及关联的CustomPainterCanvas类进行自定义绘画。

绘画小部件的一个示例是DecoratedBox ,它可以将BoxDecoration到屏幕上。 下面的例子展示了如何使用它来用渐变填充来填充屏幕:

 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小部件,它允许与 AnimationController 一起构建AnimationController

任何小部件,例如前面显示的装饰星,都可以对其属性进行动画处理。 例如,将代码重构为StatefulWidget ,因为动画是一种状态变化,并且将AnimationController传递给State类允许在构建小部件时使用动画值。

 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 ), ), ); } ); } } 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 ), ), ); } ); } } 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 ), ), ); } ); } }

在这种情况下,该值用于改变小部件的大小。 每当动画值发生变化时,都会调用builder函数,从而导致星形的大小在 750 毫秒内变化,从而产生效果比例:

动画
图标大小动画

使用本机功能

平台渠道

为了在 Android 和 iOS 上提供对原生平台 API 的访问,Flutter 应用程序可以使用平台通道。 这些允许 Flutter Dart 代码将消息发送到托管的 iOS 或 Android 应用程序。 许多可用的开源插件都是使用平台通道上的消息传递构建的。 要了解如何使用平台通道,Flutter 文档包含一个很好的文档,该文档演示了如何访问本机电池 API。

结论

即使在 beta 阶段,Flutter 也为构建跨平台应用程序提供了很好的解决方案。 凭借其出色的工具和热重载,它带来了非常愉快的开发体验。 丰富的开源软件包和优秀的文档使其易于上手。 展望未来,除 iOS 和 Android 之外,Flutter 开发人员将能够针对 Fuchsia。 考虑到引擎架构的可扩展性,看到 Flutter 也登陆各种其他平台并不让我感到惊讶。 随着社区的发展,现在是加入的好时机。

下一步

  • 安装颤振
  • 飞镖语言之旅
  • Flutter 代码实验室
  • Flutter Udacity 课程
  • 文章源代码