Flutter简述
Flutter是一个UI SDK, 可以进行移动端(iOS, Android),Web端, 桌面,它是一个跨平台解决方法。
Flutter的特点:美观,快速,高效,开放。
美观:Flutter内置了美丽的Material Design和 Cupertino widget, 方便开发出美丽的页面。
快速:Flutter的UI渲染性能好,在生产环境下,Flutter将代码编译成机器码执行,并充分利用GPU的图形加速能力,可以实现60FPS。Debug环境下则使用JIM,提升了开发效率。
Flutter引擎使用C++编写,包含了高效的Skia 2D渲染引擎,Dart运行时和文本渲染库。
高效:Hot reload, 在前端不是新鲜的东西,在移动端却并不常见。
开放:Flutter是开放的,它是一个完全开源的项目。
Flutter有一统大前端的野心,并且它正在侵蚀iOS,Andriod这些原生开发。
跨平台解决方案历史
第一个阶段:通过webView
iOS端有UIWebView, Android端WebView
最早出现的跨平台框架是基于JavaScript和WebView的,代表框架有PhoneGap, Apache Cordova, Ionic等。
主要是通过Html, Css开发页面。对于调用的一些本地服务如相机,蓝牙等。需要通过JS进行桥接调用Native功能的一些功能代码,本身的性能和体验并不理想。
第二阶段:React Native
RN是FaceBook早先开源的JS框架React在原生移动平台的衍生产物,目前支持iOS和安卓两大平台。
RN是使用JS语言,使用类似于HTML的JSX, 以及CSS来做移动开发。
使用原生自带的UI组件实现核心的渲染引擎,从而保证了良好的渲染性能。
但是RN的本质是通过JavaScript VM调用原生接口,通信相对比较低效,并且框架本身不负责渲染,而是间接通过原生进行渲染的。
第三个阶段:Flutter
拥有自渲染闭环,是理想的跨平台框架。
安卓原生渲染流程:
1.通过Java语言,调用Android框架提供的framework的API 写出页面布局。
2.页面布局通过Android框架中framework进行翻译,将翻译的结果提交给Skia。
3.Skia给CPU/GPU 提供数据进行渲染。
Flutter渲染流程:
1.通过Dart语言,调用Flutter框架提供的framework的API 写出页面布局。
2.页面布局通过Flutter框架中framework进行翻译,将翻译的结果提交给Skia。
3.Skia给CPU/GPU 提供数据进行渲染。
从上面可以发现,Flutter和安卓的渲染流程是一样的。
RN的渲染流程
1.通过JS, JSX, CSS, Redux等调用React框架提供的API编写页面布局。
2.React框架通过JavaScript VM, Bridge将JS布局转换成原生布局。
3.原生页面布局通过Android框架中framework进行翻译,将翻译的结果提交给Skia。
4.Skia给CPU/GPU 提供数据进行渲染。
RN渲染增加了1,2步骤的转换,造成了性能消耗。所以RN的性能没有Flutter的高。
Flutter不需要依赖原生控件,利用Skia绘图引擎,直接通过CPU,GPU进行绘制。和安卓的原生绘制流程一样。
而像RN框架,必须先通过桥接的方式转成原生调用,然后再进行渲染。存在性能消耗。
Flutter绘制原理
1.GPU将VSync绘制信号同步到UI线程。
2.UI线程用Dart将Flutter代码构建图层树Layer Tree。
3.图层树Layer Tree在GPU线程内的Compositor进行合成。
4.Compositor合成的结果交给Skia进行渲染。
5.Skia渲染的结果通过OpenGL或Vulkan交给GPU进行图像绘制。
6.GPU绘制完成后把图像放入到双缓存的Back Buffer,等下一个VSync来时,系统从Back Buffer复制到Frame Buffer并产生新一轮的CPU/GPU绘制过程。
上面是Flutter完整的渲染闭环,这是它和ReactNative的本质区别。
ReactNative是没有自己的渲染闭环的,它是通过JS VM调用系统组件,使用的iOS、安卓的原生渲染。
图像的显示原理
在屏幕上看到的任何东西都是图像,包括图片,GIF图像,视频。
当图片连续播放的频率超过16帧时,人眼就会感到非常流畅。当小于16帧时就会感到卡顿。
帧率(fps): Frames Per Second, 每秒生成多少帧图像。
刷新率:显示屏的频率,比如iPhone的屏幕每秒刷新60下,表示为60Hz。
帧率和刷新率的关系
GPU/CPU生成图像(每秒生成多少张图片是帧率)放入Buffer中,屏幕从Buffer中取图像,刷新(每秒刷新多少次是刷新率)后显示。这是一个生产者-消费者模型。
很容易产生的问题是:GPU在新的一帧图片写入一半时,屏幕从中取出图像展示。此时会取出一张上半部分和下半部分不一致的图像。
显示出来的图像出现上半部分和下半部分明显偏差的现象,我们称为“tearing”(撕裂)。
双重缓存和VSync
为了解决“tearing”(撕裂)问题,就出现了双重缓存和VSync。
两个缓存分别为Back Buffer和Frame Buffer, GPU向Back Buffer写数据,屏幕从Frame Buffer读数据。VSync信号负责从Back Buffer到Frame Buffer的复制操作。底层的复制是用“指针交换”的假复制,效率高。
工作流程:
某个时间点,一个屏幕刷新周期完成,VSync信号产生,先完成复制操作,然后通知CPU/GPU绘制一帧图像。
复制操作完成后,开始下一个刷新周期,将刚复制到Frame Buffer的数据显示到屏幕上。
在这种模型下,只有当VSync信号产生时,CPU/GPU才开始绘制。
双重缓存存在的问题
双重缓存的缺陷:当CPU/GPU绘制一帧的时间过长(比如超过16ms一个刷新周期时),会产生Jank(画面停顿,甚至空白),VSync信号是在一个刷新周期结束后产生的。
三重缓存
在每次VSync信号来时,多缓存一个Buffer作为备用。
渲染引擎skia
skia是Flutter向GPU提供数据的途径。
skia是一个C++编写的开源形库。
目前skia是安卓的官方图像引擎,所以Flutter的安卓应用SDK无需内嵌Skia引擎。
而对于iOS来说,Skia引擎需要嵌入到iOS SDK中,替代了iOS必源的Core Graphics / Core Animation / Core Text, 所以Flutter iOS SDK打包的APP包体积比安卓的大。
因为底层渲染能力的统一,上层开发接口和功能也就是统一的。skia保证了同一套代码调用在iOS和Android上渲染效果是完全一致的。
创建一个Flutter项目
命令行创建flutter项目
项目名称不支持中文,不支持大小,可以使用_进行链接
flutter create flutter_demo
VSCode环境安装插件
Flutter, Dart, Code Runner
Flutter项目启动方式有三种:冷启动,热启动(hotReload),热重载(hotRestart)。
冷启动:代码和Flutter框架都没有加载和运行,需要从磁盘加载到内存进行运行,过程比较久,通常需要1min-5min。
热启动 hotReload:重新运行widget中的build方法。
热重载 hotRestart:重新运行APP的main入口函数,重新运行一遍程序。
Material是什么
Material是google推出的套设计风格,或者叫设计规范。里面包含了很多设计规范,如:颜色,文字排版,响应动画与过度等。
在Flutter中高度集成Material风格的Widget。
Widget是什么
Flutter中万物皆Widget。在iOS或安卓中,我们的页面有很多种类:应用Application, 视图控制器ViewController, 视图View, Button按钮等。
但是在Flutter中,这些东西都是不同的Widget。
而在我们的APP中,所有能看到的内容几乎都是Widget, 甚至内边距设置都是使用Padding的Widget来做的。
Flutter项目的入口
Flutter项目的入口是lib/main.dart下的main函数。
在main函数内部,需要运行函数runApp(widget app);, runApp函数是Flutter提供的一个全局APP运行函数入口。
runApp(widget app);
最简单的Flutter项目Demo
import 'package:flutter/material.dart'; void main(List<String> args) { runApp(Text('Hello Flutter')); }
此时报错:没有找到排版方向
原因是Flutter是面向全世界很多国家的,有的国家排版是从左往右,有的是从右往左。所以需要用户自己设置。
这里需要明确指定排版方向:TextDirection.ltr。
在Flutter中万物都是widget,所以如果要设置center, padding ,margin等都是写对应的widget。
修改报错后,如下:
void main(List<String> args) { runApp( Center( child:Text('Hello Flutter', textDirection: TextDirection.ltr, style: TextStyle( color: Colors.red, fontSize: 30, ), ) ) ); }
MaterialApp:采用了Google的Material设计设计规范的Widget,里面默认设置了文字排版方向等设置。
Scaffold:脚手架Widget, 用于快速搭建页面机构,提供了不同位置的命名可选参数。
import 'package:flutter/material.dart'; /* MaterialApp:采用了Google的Material设计设计规范的Widget,里面默认设置了文字排版方向等设置。 Scaffold:脚手架Widget, 用于快速搭建页面机构,提供了不同位置的命名可选参数。 debugShowCheckedModeBanner: 去掉右上角的debug条 */ void main(List<String> args) { runApp( MaterialApp( debugShowCheckedModeBanner: false, home:Scaffold( appBar: AppBar(title: Text('第一个Flutter程序'),), body: Center( child:Text('Hello Flutter', style: TextStyle( color: Colors.blue, fontSize: 30, ), ) ), ) ) ); }
StatelessWidget
StatelessWidget:在运行过程中,组件内容是固定的,没有状态修改的。
Widget中没有数据改动,只使用固定的数据展示或者只使用从父Widget继承过来的Widget时,使用StatelessWidget。
继承StatelessWidget产生的子Widget需要重新build方法,并在build方法里返回要展示的widget。
build方法是无法主动调用的。只能在数据改动时系统来调用。
build方法调用时机:
1.当StatelessWidget第一次插入到Widget树时(第一次被创建时)
2.当父Widget发生改变时,子Widget需要被重新构建
3.当依赖的InheritedWidget的数据发送改变时,被重新调用。
StatefulWidget
在运行过程中,状态(data)会产生改变,导致页面展示内容发生改变。
如果想使用StatefulWidget创建有状态变化的Widget, 需要一个State对象一起来实现。
StatefulWidget内部无法写var属性, 因为它继承自Widget,Widget是被@immutable修饰,不可改变。所以它的状态改变要在别的类(State)中实现。
State:在创建的State子类中添加var属性,并将其与Widget状态绑定,当有新的状态改变时,需要调用setState((){})进行更新状态
Flutter的状态更新和React的机制一样,需要调用setState通知框架进行页面更新。
与Vue不同的是Vue实例使用的是双向绑定,内部对属性做了监听,无需手动调用setState进行通知更新。
/* StatefulWidget内部无法写var属性, 因为它继承自Widget,Widget是被@immutable修饰,不可改变。所以它的状态改变要在别的类(State)中实现。 State:在创建的State子类中添加var属性,并将其与Widget状态绑定,当有新的状态改变时,需要调用setState((){})进行更新状态 Flutter的状态更新和React的机制一样,需要调用setState通知框架进行页面更新。 与Vue不同的是Vue实例使用的是双向绑定,内部对属性做了监听,无需手动调用setState进行通知更新。 */ class PageContent extends StatefulWidget { @override State<StatefulWidget> createState() { return PageContentState(); } } class PageContentState extends State<PageContent> { var flag = true; @override Widget build(BuildContext context) { return Center( child:Row( mainAxisAlignment: MainAxisAlignment.center, children:[ Checkbox( value: flag, onChanged: (value) { setState(() { flag = value; }); },), Text('Hello World') ] ) ); } }
声明式编程和命令式编程
iOS,安卓使用的是命令式编程,平时涉及到的是属性,成员变量。
vue, react, flutter是声明式编程,平时涉及到的是State状态,平时开发是只需要管好状态,显示内容有框架自动帮忙更新上去。
Flutter修饰符
@immutable修饰符表示Widget类是不可改变的,里面的属性都是final类型的。
required可选变量 必传修饰符, 用于表示命名可选变量为必传参数。
@immutable abstract class Widget extends DiagnosticableTree const Checkbox({ Key? key, required this.value, this.tristate = false, required this.onChanged,