Flutter 一个跨平台的UI构建工具,可以使用一套代码搭建Andriod,IOS,和 desktop 等不同的应用,达到一次编写到处运行。
Flutter 架构图
embedder
embedder(嵌入器)i是和底层的操作系统进行交互的部分。因为flutter最终要将程序打包到对应的平台中,所以这个嵌入器需要和底层的平台接口进行交互。
flutter 打包程序,可以作为整个应用程序,也可以作为现有程序的一部分被嵌入使用。
engine
flutter中最核心的部分。
其存在是为了支持Dart Framework 的运行。它提供了Flutter的核心API,包括作图、文件、操作系统、网络IO、dart 运行时环境等核心功能。
engine 主要是通过dart:ui 暴露给Flutter framework 层的。
Flutter framework
用户编程的接口。我们的应用程序需要和Flutter framework进行交互,最终构建出一个应用程序。
Flutter framework 主要是使用dart语言编写的。
有最基础的foundational 包,和构建在其上的animation、painting、gestures。
再上面就是rendering层,rendering为我们提供了动态构建可渲染对象树的方法,通过这些方法,我们可以对布局进行处理。
接着是widgets layer,它是rendering层中对象的组合,表示一个小挂件。
最后是Material和Cupertino库,这些库使用widgets层中提供的小部件,组合成不同风格的控件集。
Widgets
小插件
子widgets可以共享父widgets的上下文环境。
缺点:代码层级结构特别多。
Widgets的可扩展性
不依赖于操作系统提供的接口。
好处:一切都是由Flutter自己控制,使用者可以在Flutter的基础上进行无限扩展,而不用受限于系统底层的实现限制。
另一方面,这样可以减少Flutter在呈现过程中在Flutter代码和平台代码之间来回切换,减少了性能瓶颈,提升效率。
最后,因为UI的实现和底层的操作系统是分离的,所以Flutter的APP在不同的平台上可以有统一的外观和实现,可以保证风格的统一。
Widgets 的状态管理
Widgets 表示的是不可变的用户UI界面结构。虽然结构是不能变化的,但是Widgets里的状态是可以动态变化的。
根据Widgets中是否包含状态,Widgets可以分为stateful和stateless widget对应的类是StatefulWidget和StatelessWidget。
- 对于有些Widgets来说,比如icon或者Label,它里面本身就不需要状态,这些Widgets就是StatelessWidget。
- 但是如果有些Widgets中的某些内容可能需要根据用户或者其他原因来动态变化·,则就需要使用StatefulWidget。
之前提到Widgets是不可变的,StatefulWidget中的可变数据是存放在对应的State中的,所以StatefulWidgets本身并没有build方法,所有用户界面都是通过State对象来构建的。
当State 发生变化时,需要调用setState()方法来通知flutter框架来调用State的build方法,从而将变化反馈到用户界面中。
StatefulWidget是带有状态的,那这些状态是如何管理和传递的?
- State本身提供了一个build方法,用于构建初始的状态:
Widget build(BuildContext context);
如果在一个StatefulWidget中需要嵌入另一个StatefulWidget,那么可以在其对应的State中调用另外一个StatefulWidget的构造函数,将要传递的数据,以构造函数参数的形式传递给子Widget。
但是如果嵌套层级过多的话,这种构造函数的传递方式,显然不能满足我们的要求。
于是Flutter提供了也给InheriedWidget类,如果我们自定义的类需要共享数据给子Widgets,则可以继承InheritedWidget。
InheritedWidget 作用:
- 子Widget可以通过Inherited widgets提供的静态of方法拿到离它最近的父Inherited widgets实例。
- 当Inherited widgets改变state之后,会自动触发state消费者的rebuild行为。
Inherited widgets类的定义:
abstract class InheritedWidget extends ProxyWidget {
const InheritedWidget({ Key? key, required Widget child })
: super(key: key, child: child);
@override
InheritedElement createElement() => InheritedElement(this);
@protected
bool updateShouldNotify(covariant InheritedWidget oldWidget);
}
InheritedWidget是对实际Widget对象的代理,还将InheritedWidget封装到了InheritedElement中。(InheritedElement是底层通知机制的实现)
InheritedElement 还添加了一个updateShouldNotify,控制当前InheritedWidget rebuild的时候,是否需要rebuild继承它的子Widget。
栗子🌰:
class FrogColor extends InheritedWidget {
const FrogColor({
Key? key,
required this.color,
required Widget child,
}) : super(key: key, child: child);
final Color color;
static FrogColor of(BuildContext context) {
final FrogColor? result = context.dependOnInheritedWidgetOfExactType<FrogColor>();
assert(result != null, 'No FrogColor found in context');
return result!;
}
@override
bool updateShouldNotify(FrogColor old) => color != old.color;
}
FrogColor
中定义了一个Color
属性,当Color
发生变化的时候,就会调用updateShouldNotify
。- of 方法,接受的参数是BuildContext,然后调用
context.dependOnInheritWidgetOf
去查找离该context最近的FrogColor。 - 为什么要使用of方法对context.dependOnInheritedWidgetOfExactType进行封装?
- 因为context.dependOnInheritedWidgetOfExactType方法不一定能够找到要找到的对象,所以我们需要进行一些异常值的处理。
- 有可能of方法返回的对象和context.dependOnInheritedWidgetOfExactType中查找的对象不一样,这都是可以的。
of 方法的具体使用:
class MyPage extends StatelessWidget {
const MyPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: FrogColor(
color: Colors.green,
child: Builder(
builder: (BuildContext innerContext) {
return Text(
'Hello Frog',
style: TextStyle(color: FrogColor.of(innerContext).color),
);
},
),
),
);
}
}
还有个问题,of方法传入的是BuildContext对象(必须是InheritedWidget对象本身的后辈,也就是说在对象树中,必须是InheritedWidget 的子树)。
除了InheritedWidget,Flutter还提供了许多状态管理的工具,比如provider、bloc、flutter_hooks等。
渲染和布局
渲染就是将上面我们提到的widgets转换成用户肉眼可以感知的像素的过程。
Flutter作为一种跨平台的框架,和普通的跨平台框架或原生框架的区别:
- 原生框架:
- andriod:调用的是andriod框架的java代码,通过调用andriod系统库提供的进行绘制的组件,最后调用底层的Skia来进行绘制。Skia是一种用C/C++编写的图形引擎,它调用CPU或GPU在设备上完成绘制。
- 常见的跨平台框架是如何运行的?
- 在原生的代码框架上又封装了一层。通常使用JavaScript这样的解释性语言进行编写,然后编写的代码再和andriod的Java或IOS的Objective-C系统库进行交互。这样的结果就是在UI交互或调用之间会造成显著的性能开销。这也就是通用的跨平台语言不如原生的性能好的原因。
- 但是flutter不一样,它并不是用系统自带的UI控件,而是拥有自己的实现。Flutter代码会直接被编译成使用Skia进行渲染的原生代码,从而提升渲染效率。
flutter的渲染流程:
- 调用build方法,生成一个widget集合(Container这个widget是由很多个其它的widget组成的,所以,Container会生成widget树。)
- 在build的过程中,会被转换为element tree。一个element和一个widget对应。
element表示的是widget的实例,flutter中有两种类型的element,分别是:ComponentElement和RenderObjectElement。ComponentElement是其他Element的容器,而RenderObjectElement是真正参与layout和渲染的element。
因为 Widget本身是不可变的,所以任何对于Widget的修改都会返回一个新的Widget。那么是不是所有的变动,都会导致整个element tree重新渲染呢?
答:不会,flutter仅会重新渲染需要被重新绘制的element。
渲染树🌲如何构建的?
渲染树中的每个元素叫做RenderObject,它定义了布局和绘制的抽象模型。
RenderObjectElement 会在渲染的时候转换成为RenderObject。
不同的Render element会转换成不同的Render对象。