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

作者: Mike Bluestein | 译:孙印凤 原文地址:

[https://www.smashingmagazine.com/2018/06/google-flutter-mobile-development/] 【译者注:链接序号对应下面索引列表,另外可以点击阅读原文查看详细的链接文章】

简介:Flutter 使得创建跨平台移动端应用变得轻而易举。本文将介绍 Flutter 并将其与其他移动端开发平台进行比较,还会阐述如何使用它来构建应用程序。

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 上的图片:

image

工具选项

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

image
image.gif
image

热加载

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

测试

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

// 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 文件中添加依赖,如下所示:

dependencies:

  image_picker: 
"^0.4.1"

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

import
 
'package:image_picker/image_picker.dart';

Widgets

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

简单的 UI WIDGET 示例

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

import
 
'package:flutter/material.dart'
;


void
 main() {

  runApp(

    
Container
(color: 
Colors
.lightBlue)

  );

}

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

image.gif

无状态和有状态 widgets

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

class
 
MyWidget
 
extends
 
StatelessWidget
 {

  
@override

  
Widget
 build(
BuildContext
 context) {

    
return
 
Container
(color: 
Colors
.lightBlue);

  }

}

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

 
MyStatefulWidget
 
extends
 
StatefulWidget
 {


  
MyStatefulWidget
();


  
@override

  
State
 createState() {

    
return
 
MyWidgetState
();

  }

}

有状态的 widget 将会返回一个 State 类,该类负责为给定的 state 构建 widget 树。当 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
)),

            ],

          )

        )

      )

    );

  }

}

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

image.gif

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

布局

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

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

      ],

    );

  }

}
image.gif

响应触摸

触摸交互是由手势处理的,手势是封装在 GestureDetector 类中。由于它也是个 widget,添加手势识别和在 GestureDetector 中封装子 widgets 一样简单。例如,在一个 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: 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 和渐变填充填充屏幕。

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

        ),

      ),

    );

  }

}
image

动画

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

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毫秒时产生了规模效应。

image

使用原生的功能

平台通道

为了给 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/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容