Introduction
Flutter is a cross-platform UI construction tool developed by google. The latest version of flutter is 3.0.5. Using flutter, you can use a set of codes to build applications on different platforms such as android, IOS, web and desktop. To achieve the purpose of writing once and running everywhere.
When it comes to write once and run everywhere, you may think of java. So is flutter similar to java?
For JAVA, after writing JAVA code, it is compiled into class bytecode, and then this class bytecode can be run on any platform without any conversion. The underlying principle is that JAVA has developed a JVM that adapts to different operating systems and platforms, and the class actually runs in the JVM, so it is indifferent to which platform the bottom layer runs on. All adaptations are performed by the JVM.
Flutter is actually more like C or C++. Although the code is the same, it needs to be compiled into different binary files according to different platforms. The same is true for Flutter. Although we use the same set of dart code to write Flutter programs, we need different commands to compile into commands and installation packages for different platforms.
Of course, during the development process, flutter provides a virtual machine that implements the function of hot reload. After the code is modified, it can be reloaded immediately without recompiling the entire code.
FLutter is so amazing, so how exactly does it work?
Flutter's architecture diagram
Let's first look at the architecture diagram of Flutter. Of course, this architecture diagram is official, and the official architecture diagram represents authority:
From the above figure, we can see that Flutter's architecture can be divided into three parts, from bottom to top are embedder, Engine and Framework.
embedder
An embedder can be called an embedder, which is the part that interacts with the underlying operating system. Because flutter will eventually package the program into the corresponding platform, the embedder needs to interact with the underlying platform interface.
Specifically, Java and C++ are used for the Android platform, Objective-C/Objective-C++ is used for the iOS and macOS platforms, and C++ is used for the Windows and Linux platforms.
Why is C++ so powerful? It can be seen here, basically all the underlying things are written in C++.
Back to the embedder, why is it called an embedder? This is because programs packaged by Flutter can be embedded as part of an entire application or as part of an existing program.
engine
Engine is also called flutter engine, which is the core part of flutter.
The Flutter engine is basically written in C++. The engine exists to support the operation of the Dart Framework. It provides the core API of Flutter, including core functions such as drawing, file operations, network IO, and dar runtime environment.
The engine is mainly exposed to the Flutter framework layer through dart:ui.
Flutter framework
This layer is the interface for user programming. Our application needs to interact with the Flutter framework to finally build an application.
The Flutter framework is mainly written in the dart language.
Framework From bottom to top, we have the most basic foundational package, and animation, painting and gestures built on it.
Above is the rendering layer. Rendering provides us with methods to dynamically build a tree of renderable objects. Through these methods, we can process the layout.
Then comes the widgets layer, which is a combination of objects in the rendering layer, representing a widget.
Finally there are the Material and Cupertino libraries, which use the widgets provided in the widegts layer to combine different styles of widget sets.
The Flutter framework is built layer by layer in this way.
Of course, the above embedder and engine are relatively low-level things, we only need to know that Flutter has such a thing, and it is used like this.
What is really relevant to our programmers is the Flutter framework. Because we need to deal with the Flutter framework in the process of writing code.
Next, we focus on several core parts of the Flutter framework.
Widgets
Widgets translated into Chinese means small plug-ins. Widgets are the foundation of the user interface in Flutter. The user interfaces you can observe in the flutter interface are all Widgets.
Of course, these large Widgets are composed of small Widgets, and these small Widgets are composed of smaller Widgets.
In this way, the hierarchical dependency structure of Widgets is formed, and the relationship of these hierarchical structures is associated through the child Widget in the Widget.
In this hierarchy, child Widgets can share the context of parent Widgets.
How are Widgets in Flutter different from similar combinations of Widgets in other languages?
The biggest difference between them is that there are more Widgets in Flutter, and each Widgets focuses on smaller functions. Even if it is a small function, the corresponding Widgets can be found in Flutter.
The advantage of this is that you can use any combination of different, very basic Widgets to build very complex, personalized large Widgets.
Of course, its shortcomings are also very obvious, that is, there are too many Widgets in the code, resulting in a lot of hierarchical structures in the code, which may be dazzling.
For a simple example, Container is a basic container widget provided by flutter, we usually use it like this:
Container(
constraints: BoxConstraints.expand(
height: Theme.of(context).textTheme.headline4!.fontSize! * 1.1 + 200.0,
),
padding: const EdgeInsets.all(8.0),
color: Colors.blue[600],
alignment: Alignment.center,
child: Text('Hello World',
style: Theme.of(context)
.textTheme
.headline4!
.copyWith(color: Colors.white)),
transform: Matrix4.rotationZ(0.1),
)
We passed constraints, padding, color, alignment, child, transform and other information into the Container.
Let's take a guess. Which of these information is used to build the Widget?
The first thing that everyone thinks of should be child, which itself is a Widget, used to represent the child objects contained in the Container, which is easy to understand.
However, in addition to the Widget of child, other constraints, padding, color, alignment, transform, etc. are all elements that constitute a Widget!
Let's take a look at the build method of Container:
Widget build(BuildContext context) {
Widget? current = child;
if (child == null && (constraints == null || !constraints!.isTight)) {
current = LimitedBox(
maxWidth: 0.0,
maxHeight: 0.0,
child: ConstrainedBox(constraints: const BoxConstraints.expand()),
);
}
if (alignment != null)
current = Align(alignment: alignment!, child: current);
final EdgeInsetsGeometry? effectivePadding = _paddingIncludingDecoration;
if (effectivePadding != null)
current = Padding(padding: effectivePadding, child: current);
if (color != null)
current = ColoredBox(color: color!, child: current);
if (clipBehavior != Clip.none) {
assert(decoration != null);
current = ClipPath(
clipper: _DecorationClipper(
textDirection: Directionality.maybeOf(context),
decoration: decoration!,
),
clipBehavior: clipBehavior,
child: current,
);
}
if (decoration != null)
current = DecoratedBox(decoration: decoration!, child: current);
if (foregroundDecoration != null) {
current = DecoratedBox(
decoration: foregroundDecoration!,
position: DecorationPosition.foreground,
child: current,
);
}
if (constraints != null)
current = ConstrainedBox(constraints: constraints!, child: current);
if (margin != null)
current = Padding(padding: margin!, child: current);
if (transform != null)
current = Transform(transform: transform!, alignment: transformAlignment, child: current);
return current!;
}
As you can see from the code, Container first creates a LimitedBox, then embeds it in Align, and then embeds it in Padding, ColoredBox, ClipPath, DecoratedBox, ConstrainedBox, Padding and Transform in turn. All these objects are Widgets.
Here you should understand the design idea of Widget in Flutter. Everything can be a Widget in Flutter.
Extensibility of Widgets
Compared with other cross-platform implementations such as React native, which are compiled into native language features, Flutter has its own implementation for each UI, rather than relying on the interface provided by the operating system.
The advantage of this is that everything is controlled by Flutter itself, and users can expand infinitely on the basis of Flutter without being limited by the implementation restrictions at the bottom of the system.
On the other hand, this can reduce Flutter's conversion back and forth between Flutter code and platform code during the rendering process, reducing performance bottlenecks and improving efficiency.
Finally, because the implementation of the UI is separated from the underlying operating system, Flutter's APP can have a unified appearance and implementation on different platforms, which can ensure the unity of style.
Widgets state management
Widgets represent immutable user UI interface structures. Although the structure cannot be changed, the state in Widgets can be changed dynamically.
Widgets can be divided into stateful and stateless widgets according to whether they contain state or not. The corresponding classes are StatefulWidget and StatelessWidget.
For some Widgets, such as icon or Label, it does not need state in itself, these Widgets are StatelessWidget.
But if some content in some Widgets may need to change dynamically according to the user or other reasons, then you need to use StatefulWidget.
As mentioned earlier, Widgets are immutable, and the variable data in StatefulWidget is stored in the corresponding State, so StatefulWidgets itself does not have a build method, and all user interfaces are built through State objects.
When the State changes, you need to call the setState() method to notify the flutter framework to call the State's build method, so that the changes are fed back to the user interface.
Since StatefulWidget is stateful, how are these states managed and transmitted?
State itself provides a build method for building the initial state:
Widget build(BuildContext context);
If you need to embed another StatefulWidget in a StatefulWidget, you can call the constructor of another StatefulWidget in its corresponding State, and pass the data to be passed to the child Widget in the form of constructor parameters.
Of course it is fine to do so. But if there are too many nested levels of components, this way of passing the constructor obviously cannot meet our needs.
So Flutter provides an InheritedWidget class. If our custom class needs to share data to child Widgets, we can inherit InheritedWidget.
Inherited widgets have two functions: First, the child widget can get the nearest parent Inherited widgets instance through the static of method provided by Inherited widgets.
Second, when Inherited widgets change the state, they will automatically trigger the rebuild behavior of the state consumer.
Let's first look at the definition of the inherited widgets class:
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);
}
You can see that InheritedWidget is a proxy for the actual Widget object, and also encapsulates InheritedWidget into InheritedElement.
Not much to explain InheritedElement here, InheritedElement is the implementation of the underlying notification mechanism.
We see that InheritedWidget also adds an updateShouldNotify, this method can provide us to control whether the current InheritedWidget needs to be rebuilt to inherit its child Widget when rebuilt.
Let's look at a specific implementation of an InheritedWidget:
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;
}
A Color property is defined in FrogColor. When the Color changes, updateShouldNotify will be called.
In addition, FrogColor also provides a of method, the accepted parameter is BuildContext, and then call context.dependOnInheritedWidgetOfExactType to find the FrogColor closest to the context.
Why use the of method to encapsulate context.dependOnInheritedWidgetOfExactType? This is because the context.dependOnInheritedWidgetOfExactType method may not be able to find the object it is looking for, so we need to deal with some abnormal values.
In addition, it is possible that the object returned by the of method is different from the object found in context.dependOnInheritedWidgetOfExactType, which is all possible.
Let's take a look at the specific use of the of method:
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),
);
},
),
),
);
}
}
There is another problem, the of method passes in the BuildContext object. Note that the BuildContext here must be the descendant of the InheritedWidget object itself, that is to say, in the object tree, it must be a subtree of InheritedWidget. Consider the following example:
class MyOtherPage extends StatelessWidget {
const MyOtherPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: FrogColor(
color: Colors.green,
child: Text(
'Hello Frog',
style: TextStyle(color: FrogColor.of(context).color),
),
),
);
}
}
In this example, the context in the FrogColor.of method is the parent context of FrogColor, so the FrogColor object cannot be found. Such use is wrong.
Of course, in addition to InheritedWidget, Flutter also provides many state management tools, such as provider, bloc, flutter_hooks, etc., which are also very useful.
rendering and layout
Rendering is the process of converting the widgets we mentioned above into pixels that the user can perceive with the naked eye.
As a cross-platform framework, what is the difference between Flutter and ordinary cross-platform frameworks or native frameworks?
First, consider native frameworks. Taking android as an example, the java code of the andorid framework is called first, the drawing components provided by the android system library are called, and finally the underlying Skia is called for drawing. Skia is a graphics engine written in C/C++ that calls the CPU or GPU to complete the drawing on the device.
So how do common cross-platform frameworks work? They actually encapsulate another layer on top of the native code framework. It is usually written in an interpreted language such as javascript, and then the written code interacts with andorid's JAVA or IOS's Objective-C system library. The result is a significant performance overhead between UI interactions or calls. This is why general-purpose cross-platform languages are not as performant as native ones.
But flutter is different. It does not use the UI controls that come with the system, but has its own implementation. Flutter code will be compiled directly into native code that uses Skia for rendering, thereby improving rendering efficiency.
Next, let's take a look at the entire process of flutter from code to rendering. First look at a piece of code:
Container(
color: Colors.blue,
child: Row(
children: [
Image.network('http://www.flydean.com/1.png'),
const Text('A'),
],
),
);
The above code is to build a Container widget. When flutter wants to render the widget, it will call the build() method and generate a widget collection.
Why a Widget Collection? As we have also analyzed above, the Container widget is composed of many other widgets, so the above Container will generate the following widget tree:
The above are the widgets generated in the code. These widgets will be converted into element trees during the build process. An element corresponds to a widget.
The instance of the widget represented by element. There are two types of elements in flutter, namely: ComponentElement and RenderObjectElement.
ComponentElement is a container for other Elements, and RenderObjectElement is the element that actually participates in layout and rendering.
Because the Widget itself is immutable, any modification to the Widget will return a new Widget. So will all changes cause the entire element tree to be re-rendered?
The answer is no, flutter will only re-render elements that need to be re-drawn.
Next, let's look at how the rendering tree is constructed. Each element in the rendering tree is called a RenderObject, which defines an abstract model for layout and rendering.
The RenderObjectElement we mentioned above will be converted to RenderObject when rendering, as follows:
Of course, different Render elements will be converted into different Render objects.
Summarize
Widget and Layout are the parts that we often need to use when we are actually doing flutter development, and everyone needs to have an in-depth understanding and mastery.
For more information, please refer to http://www.flydean.com/01-flutter-architectural/
The most popular interpretation, the most profound dry goods, the most concise tutorials, and many tricks you don't know are waiting for you to discover!
Welcome to pay attention to my official account: "Program those things", understand technology, understand you better!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。