1.Flutter是什么?
官方介绍:Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。 Flutter可以与现有的代码一起工作。在全世界,Flutter正在被越来越多的开发者和组织使用,并且Flutter是完全免费、开源的。
从官方介绍可以看到,Flutter有如下特点:
- 跨平台:现在Flutter至少可以跨4种平台,甚至支持嵌入式开发。我们常用的有Linux、Android、IOS,甚至可以在谷歌最新的操作系统上Fuchsia进行运行,经过第三方扩展,甚至可以跑在MacOS和Windows上,到目前为止,Flutter算是支持平台最多的框架了,良好的跨平台性,直接带来的好处就是减少开发成本。
- 原生用户界面: 它是原生的,让我们的体验更好,性能更好。用官方的话讲就是平滑而自然的滑动效果和平台感知,为您的用户带来全新的体验。
- 开源免费:我们只要学会并使用,这些都是免费的。
现在越来越多的公司加入了Flutter阵容:阿里巴巴、京东、腾讯、头条....
这里,我们通过官方的一张图来看一下Flutter的架构:
- Flutter Framework:这是一个纯 Dart实现的 SDK,它实现了一套基础库,自底向上, 用于处理动画、绘图和手势等。并且基于绘图封装了一套 UI组件库,细分为两种风格的组件(Material和Cupertino)。
- 底下两层(Foundation和Animation、Painting、Gestures)在Google的一些视频中被合并为一个dart UI层,对应的是Flutter中的
dart:ui
包,它是Flutter引擎暴露的底层UI库,提供动画、手势及绘制能力。 - Rendering层,这一层是一个抽象的布局层,它依赖于dart UI层,Rendering层会构建一个UI树,当UI树有变化时,会计算出有变化的部分,然后更新UI树,最终将UI树绘制到屏幕上,这个过程类似于React中的虚拟DOM。Rendering层可以说是Flutter UI框架最核心的部分,它除了确定每个UI元素的位置、大小之外还要进行坐标变换、绘制(调用底层dart:ui)。
- Widgets层是Flutter提供的的一套基础组件库,在基础组件库之上,Flutter还提供了 Material 和Cupertino两种视觉风格的组件库。而我们Flutter开发的大多数场景,只是和这两层打交道。
- Flutter Engine:这是一个纯 C++实现的框架层,包含了 Skia引擎(高性能渲染引擎)、Dart运行环境、文字排版引擎等,在代码调用
dart:ui
库时,调用最终会走到Engine层,然后实现真正的绘制逻辑。它可以以 JIT(即时编译)、JIT Snapshot 或者 AOT(预先编译)的模式运行 Dart代码。
更多Flutter的框架介绍,可以参考:https://book.flutterchina.club/chapter1/flutter_intro.html
2.和主流框架的对比
- Cordova:Cordova基于网页技术进行包装,利用插件的形式开发移动应用,无论是性能还是体验,Flutter都可以完胜Cordova。
- RN(React Native)/Weex:RN/Weex的效率由于是将View编译成了原生View,所以效率上要比基于Cordova的HTML5高很多,但是它也有效率问题,RN/Weex的渲染机制是基于前端框架的考虑,复杂的UI渲染是需要依赖多个view叠加。比如我们渲染一个复杂的ListView,每一个小的控件,都是一个native的view,然后相互组合叠加。想想此时如果我们的list再需要滑动刷新,会有多少个对象需要渲染。所以也就有了前面所说的RN/Weex的列表方案不友好。
- Flutter:吸收了前两者的教训之后,在渲染技术上,选择了自己实现(GDI),由于有更好的可控性,使用了新的语言Dart,避免了RN的那种通过桥接器与Javascript通讯导致效率低下的问题,所以在性能方面比RN更高一筹;有经验的开发者可以打开Android手机开发者选项里面的显示边界布局,发现Flutter的布局是一个整体.说明Flutter的渲染没用使用原生控件进行渲染。
【总结】:Flutter采用GPU渲染技术,所以性能极高。Flutter编写的应用可以达到120fps(每秒传输帧数),这也就是说,它完全可以胜任游戏的制作。而我们常说的RN的性能只能达到60fps,这也算是Flutter的一个超高竞争力。官方宣称Flutter甚至会超过原生性能。
经过上面的对比,我们来分析一下Flutter的优劣势。
2.1优势
- 性能强大,流畅。对比
weex
和react native,Flutter直接在两个平台上重写了各自的UIKit,对接到平台底层,减少UI层的多层转换,UI性能可以比肩原生。而Weex和RN,都是基于DOM树来渲染原生组件。
- 灵活、组件库易维护、UI外观保真度和一致性高。由于UI渲染不依赖原生控件,也就不需要根据不同平台的控件单独维护一套组件库,所以代码容易维护。由于组件库是同一套代码、同一个渲染引擎,所以在不同平台,组件显示外观可以做到高保真和高一致性;另外,由于不依赖原生控件,也就不会受原生布局系统的限制,这样布局系统会非常灵活。
2.2不足
- 不支持热更新。Weex和RN支持动态下发JS来支持热更新。
- 对原生的基本技能要求。虽然Flutter是跨平台的,但大部分我们的开发,是需要集成到原生中的。全新的Flutter,也需要有原生的基础知识。
- 开发思维的变换。原生开发中,我们主要基于继承的思路来进行视图的开发,比如说继承UIView,重写UIView的某个生命周期函数,再添加一些方法和属性,来完成一个自定义的View。在Flutter中,我们不能在它的生命周期中修改属性,二是需要嵌套组合几种
Widget
,例如Row
,Container
,ListView
等Widget
。 - 资源的添加和使用,相对来说比较麻烦(现在可以添加文件夹了:https://flutter.dev/docs/development/ui/assets-and-images)。
- Apple生态圈。
- 安装包大小(iOS大了12M左右;安卓大了5M左右)。
3.Flutter生态
- github地址:https://github.com/Solido/awesome-flutter
- flutter-go:https://github.com/alibaba/flutter-go
- Flutter中文网:https://book.flutterchina.club/(英文:https://flutter.dev/docs)
- 第三方库:https://pub.dartlang.org/flutter
- ....
4.认识Flutter
环境搭建参考 Flutter安装。
4.1创建Flutter工程
方式一:使用命令。
flutter create myapp //创建Flutter工程
flutter devices //查看运行的设备
flutter run //运行应用程序
flutter build ios --debug //iOS debug模式打包
flutter build ios --release //iOS release模式打包(release包不能在模拟器上正常运行)
flutter channel //查看当前channel
flutter channel beta //切换到beta channel
flutter upgrade //升级
方式二:使用Visual Studio Code/Android Studio。这里使用Android Studio进行示例。
打开Android Studio,选择“Start a new Flutter project”:
接下来会进入一个工程类型选择页面:
- Flutter Application:标准的Flutter App工程,包含标准的Dart层与Native平台层;
- Flutter Module:Flutter组件工程,仅包含Dart层实现,Native平台层子工程为通过Flutter自动生成的隐藏工程;
- Flutter Plugin:Flutter平台插件工程,包含Dart层与Native平台层的实现;
- Flutter Package:Flutter纯Dart插件工程,仅包含Dart层的实现,往往定义一些公共Widget。
这里选择“Flutter Application”,继续下一步:
后面直接Finish就可完成工程的创建。创建出来的工程结构如下:
4.2示例代码解析
打开main.dart,我们对这里的代码做一个分析:
//导入Material UI组件库,Material是一种标准的移动端和web端的视觉设计语言
import 'package:flutter/material.dart';
//应用入口
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
//build()方法用来描述如何构建UI界面
@override
Widget build(BuildContext context) {
//MaterialApp 是Material库中提供的Flutter APP框架,通过它可以设置应用的名称、主题、语言、首页及路由列表等
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
//MyHomePage 是应用的首页,它继承自StatefulWidget类,表示它是一个有状态的widget
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
//Scaffold 是 Material库中提供的一个widget, 它提供了默认的导航栏、标题和包含主屏幕widget树的body属性
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
使用“flutter run”命令运行之后,会有一段文字提示:
To hot reload changes while running, press "r". To hot restart (and rebuild state), press "R".
An Observatory debugger and profiler on iPhone XR is available at: http://127.0.0.1:53619/
For a more detailed help message, press "h". To detach, press "d"; to quit, press "q".
从上面可以看到,我们可以使用快捷键进行几个操作:
- r 键:点击后热加载,也就是重新加载。
- p 键:显示网格,这个可以很好的掌握布局情况,工作中很有用。
- o 键:切换android和ios的预览模式。
- q 键:退出调试预览模式。
下面我们从几个核心概念,对示例代码做一个解析。
4.2.1程序入口
void main() => runApp(MyApp());
上面的代码是Dart语法中特有的简写形式,展开等价于如下代码:
void main() {
return runApp(MyApp());
}
上面的runApp函数,是Flutter框架的入口,可以将给定的组件(widget)显示在屏幕上。
这里,大家可能会问一个问题?如果在main函数中,不使用runApp函数,会怎么样?
不使用runApp函数的话,程序仍会正常运行,但屏幕上什么都不会显示,相当于就是一个Dart控制台程序。
4.2.2StatelessWidget和StatefulWidget
从上面的示例代码可以看到,类MyApp继承自StatelessWidget,类MyHomePage继承自StatefulWidget。
class MyApp extends StatelessWidget {
//...
}
class MyHomePage extends StatefulWidget {
//...
}
class _MyHomePageState extends State<MyHomePage> {
//...
}
那么StatelessWidget和StatefulWidget是什么呢?
- StatefulWidget:有状态组件,持有的状态可能在Widget生命周期中发生变化。例如 Checkbox, Radio, Slider, InkWell, Form, and TextField等;
- StatelessWidget:无状态组件,是不可变的,它的属性不能改变,所有的值都是最终的。 例如Icon、 IconButton, 和Text等。
查看继承关系,这两者都继承自widget类。StatelessWidget由于不需要维护状态的场景,它通常在build
方法中通过嵌套其它Widget来构建UI,在构建过程中会递归的构建其嵌套的Widget。
从上面的代码可以看到,实现一个StatefulWidget,至少需要两个类:
- 一个是StatefulWidget类,这个类里有一个方法createState(),这个方法直接调用了State类,用于创建和Statefulwidget相关的状态。
- 一个State类,StatefulWidget类本身是不变的,但是State类在Widget生命周期中始终存在,用于更新StatefulWidget的状态。
State类的各个生命周期函数,对于我们使用StatefulWidget很重要,这里介绍一个各个生命周期的作用,具体大家可以写一个StatefulWidget,打印日志,来详细查看调用时机。
initState
:当Widget第一次插入到Widget树时会被调用,对于每一个State对象,Flutter framework只会调用一次该回调,所以,通常在该回调中做一些一次性的操作,如状态初始化、订阅子树的事件通知等。-
didChangeDependencies()
:当State对象的依赖发生变化时会被调用;例如:在之前build()
中包含了一个InheritedWidget
,然后在之后的build()
中InheritedWidget
发生了变化,那么此时InheritedWidget
的子widget的didChangeDependencies()
回调都会被调用。典型的场景是当系统语言Locale或应用主题改变时,Flutter framework会通知widget调用此回调。 -
build()
:主要是用于构建Widget子树的,会在如下场景被调用:
- 在调用
initState()
之后。 - 在调用
didUpdateWidget()
之后。 - 在调用
setState()
之后。 - 在调用
didChangeDependencies()
之后。 - 在State对象从树中一个位置移除后(会调用deactivate)又重新插入到树的其它位置之后。
-
reassemble()
:此回调是专门为了开发调试而提供的,在热重载(hot reload)时会被调用,此回调在Release模式下永远不会被调用。 -
didUpdateWidget()
:在widget重新构建时,Flutter framework会调用Widget.canUpdate
来检测Widget树中同一位置的新旧节点,然后决定是否需要更新,如果Widget.canUpdate
返回true
则会调用此回调。 -
deactivate()
:当State对象从树中被移除时,会调用此回调。在一些场景下,Flutter framework会将State对象重新插到树中,如包含此State对象的子树在树的一个位置移动到另一个位置时(可以通过GlobalKey来实现)。如果移除后没有重新插入到树中则紧接着会调用dispose()
方法。 -
dispose()
:当State对象从树中被永久移除时调用;通常在此回调中释放资源。
实际开发中,我们主要是在initState和build方法中做一些事情。
从上面也可以看到,使用StatefulWidget相对来说,比较复杂,那么有没有其他方式来管理状态呢?这里先留一个悬念,后面在实战系列里做介绍。
4.2.3MaterialApp
在MyApp的build方法中,直接返回了一个MyApp对象:
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
这个MaterialApp是什么呢?
它属于flutter/material.dart包,因此基本上每个flutter程序的代码,第一行代码都会引入这个包。这个包是 Flutter 实现 Material Design设计风格(谷歌推出的一套视觉设计语言)的基础包, 里面有文本输入框( Text)、图标( Icon)、图 片( Image)、行排列布局( Row)、 列排列布局( Column)、 Decoration (装饰器)、动画等组件。
MaterialApp代表使用Material Design设计风格的应用,里面包含了其所需要的基本控件。 一个完整的 Flutter项目就是从 Materia!App这个主组件开始的。该类的常见属性如下所示:
这里先介绍代码中出现的两个重要的属性。
Theme(主题)
为了在整个应用中使用同一套颜色和字体样式,可以使用“主题”这种方式 。 定义主题有两种方式 : 使用全局主题或使用 Theme来定义应用程序局部的颜色和字体样式。
上面示例代码中,是直接初始化了一个ThemeData对象,并配置了primarySwatch属性。实际上,ThemeData具有的属性很多:
home(主页)
home用于设置应用的主页,也就是整个应用的主组件。这里设置为MyHomePage。
4.2.4Scaffold(脚手架组件)
Scaffold 实现了基本的 Material Dsign布局。 只要是在 Material Design中定义过的单个界面显示的布局组件元素,都可以使用 Scaffold 来绘制。 上面实例中,MyHomePage对应状态类的build中,就是用Scaffold来进行页面的布局的:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
Scaffold的常见属性如下:
4.3配置文件
在我们实际开发过程中,经常会使用到一些第三方库等,在iOS中,我们通过使用CocoaPods来进行第三方库的管理;安卓中,一般使用Gradle。Flutter使用pubspec.yaml来配置第三方文件的使用,这样可以使得我们快速的应用第三方提供的功能,而不用重复造轮子。
【提醒】:Dart的包仓库地址(https://pub.dartlang.org/)
怎么使用第三方的包呢?
第一步:添加需要的包到pubspec.yaml文件,如下图所示:
第二步:运行指令 “flutter packages get”指令,获取所需要的库到工程中。
第三步:使用。
5.延伸:现有工程怎么集成Flutter?
怎么将Flutter集成到现有App中呢?这个Flutter官方提供了一个解决方案:https://github.com/flutter/flutter/wiki/Add-Flutter-to-existing-apps
这种方式的缺点是,过于依赖本地环境和侵入Native工程,这样会影响到Native的开发,那么该怎么改进呢?这里可以思考两种方案,不过本人还没有在具体的工程中进行实践。
方案一:闲鱼的 FlutterBoost
方案二:组件化架构方案
6.怎么学习?
【基础篇】:
- Flutter中文网
- Flutter实战
- Flutter免费视频第一季-环境搭建
- Flutter免费视频第二季-常用组件
- Flutter免费视频第三季-布局
- Flutter免费视频第四季-页面导航和其他
- 20个Flutter实例视频教程 让你轻松上手工作
- 一个很棒的Flutter学习资源列表
- Dart2中文文档
【实战篇】:
- Flutter实战视频-移动电商
- Flutter从入门到进阶 实战携程网App
- Flutter开发之插件入门到精通(实战)
- 更多(bilibili上搜索)...
【公司篇】:
- 闲鱼技术团队
7.参考资料
- Flutter中文网
- Flutter实战
- Flutter之禅 内存优化篇
- 燃烧我的卡路里——Flutter瘦内存、瘦包之图片渲染组件
无善无恶心之体, 有善有恶意之动, 知善知恶是良知, 为善去恶是格物。