Flutter UI system
It provides a set of Dart apis, and then underneath it implements a set of code across multiple ends through OpenGL, a cross-platform drawing library that internally calls the operating system APIS. Because the Dart API also calls the operating system API, its performance is close to native.
In Flutter, everything is a Widget. When the UI changes, we don’t modify the DOM directly. Instead, we update the state and let the Flutter UI system rebuild the UI based on the new state.
The Widget with the Element
A Widget is just configuration data for a UI Element, and a Widget can correspond to multiple elements
@immutable
abstract class Widget extends DiagnosticableTree {
const Widget({ this.key });
final Key key;
@protected
Element createElement();
@override
String toStringShort() {
return key == null ? '$runtimeType' : '$runtimeType-$key';
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
}
static bool canUpdate(Widget oldWidget, Widget newWidget) {
returnoldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key; }}Copy the code
canUpdate(...)
Is a static method, which is mainly used in the Widget tree rebuild
When reusing old widgets, specifically, whether to update the old UI tree with the new widget objectElement
Object configuration; Through its source code we can see that as long asnewWidget
witholdWidget
theruntimeType
andkey
It’s used when it’s simultaneously equalnewWidget
To updateElement
Object, or a new one will be createdElement
.
In addition, the Widget class itself is an abstract class. The core of the Widget class is the createElement() interface. In the development of Flutter, we did not directly inherit the Widget class to implement a new component. We often do this by indirectly inheriting the Widget class by inheriting StatelessWidget or StatefulWidget. StatelessWidget and StatefulWidget are directly inherited from the Widget class, which is the two important abstract classes in Flutter that introduce the two Widget models. We will focus on these two classes next.
3.1.4 StatelessWidget
Statelesswidgets are used in scenarios where state maintenance is not required. They typically build the UI by nesting other widgets in the build method, recursively building their nested widgets during the build process. Let’s look at a simple example:
Context
The build method takes a context argument, which is an instance of the BuildContext class that represents the context of the current widget in the Widget tree, and there is a context object for each widget (since each widget is a node in the Widget tree). In effect, the context is a handle to the current widget to perform “related actions” at its location in the Widget tree, such as providing a way to walk up the widget tree from the current widget and find the parent widget by widget type. Here is an example of getting a parent widget in a subtree:
lass ContextRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("The Context test"),
),
body: Container(
child: Builder(builder: (context) {
// Look up in the Widget tree for the nearest parent 'Scaffold' Widget
Scaffold scaffold = context.findAncestorWidgetOfExactType<Scaffold>();
// Return the title of the AppBar, which is actually Text("Context test ")
return (scaffold.appBar asAppBar).title; }),),); }}Copy the code
3.1.5 StatefulWidget
Like the StatelessWidget, the StatefulWidget inherits from the Widget class and overwrites the createElement() method, but returns a different Element object. In addition, a new interface, createState(), has been added to the StatefulWidget class.
abstract class StatefulWidget extends Widget {
const StatefulWidget({ Key key }) : super(key: key);
@override
StatefulElement createElement() => new StatefulElement(this);
@protected
State createState();
}
Copy the code
StatefulElement
Inherit indirectly fromElement
Class that corresponds to the StatefulWidget (as its configuration data).StatefulElement
May be called multiple times increateState()
To create a State object.createState()
It is used to create states related to the Stateful Widget, which may be invoked multiple times during the lifecycle of the Stateful Widget. For example, when a Stateful widget was inserted simultaneously into multiple locations in the Widget tree, the Flutter Framework called this method to generate a separate State instance for each location, which is essentially oneStatefulElement
Corresponds to an instance of State.
3.1.6 State
When the State is changed, the setState() method can be manually called to notify the Flutter Framework that the State has changed. Its build method is called again to rebuild the widget tree for the purpose of updating the UI.
State Life cycle
void initState()
Widget build(BuildContext context)
void didUpdateWidget(CounterWidget oldWidget)
void deactivate()
void dispose()
void didChangeDependencies()
Copy the code
We run the application and open the routing page, after the new routing page opens
I/flutter ( 5436): initState
I/flutter ( 5436): didChangeDependencies
I/flutter ( 5436): build
Copy the code
As you can see, the initState method is called first when the StatefulWidget is inserted into the Widget tree.
Then we hit the ⚡️ button to hot-reload and the console output log looks like this:
I/flutter ( 5436): reassemble
I/flutter ( 5436): didUpdateWidget
I/flutter ( 5436): build
Copy the code
You can see that neither initState nor didChangeDependencies are called, but the didUpdateWidget is called.
initState
This callback is called when the Widget is first inserted into the Widget tree. The Flutter Framework calls this callback only once for each State object, so it usually does one-time operations in this callback, such as State initialization, subscribing to subtree event notifications, etc. Cannot be called in this callbackBuildContext.dependOnInheritedWidgetOfExactType
This method is used to get the closest parent in the Widget tree to the current WidgetInheritFromWidget
About theInheritedWidget
We’ll see in a later section), because after initialization is complete, the Widget treeInheritFromWidget
It can change, so the right thing to do is toBuild ()
Method ordidChangeDependencies()
Is called in.didChangeDependencies()
: called when the dependencies of the State object change; For example: beforebuild()
Contains oneInheritedWidget
And then after thatbuild()
中InheritedWidget
It’s changed, and nowInheritedWidget
The child widgetsdidChangeDependencies()
Callbacks are called. A typical scenario is when the system language Locale or application theme changes, the Flutter Framework notifies the Widget that this callback is called.build()
This callback, which readers should be familiar with by now, is used to build Widget subtrees and is called in the following scenarios:- In the call
initState()
After. - In the call
didUpdateWidget()
After. - In the call
setState()
After. - In the call
didChangeDependencies()
After. - After a State object is removed from one location in the tree (deactivate is called), it is reincarnated at another location in the tree.
- In the call
reassemble()
This callback is provided specifically for development debugging purposes and is called on hot reload. This callback is never called in Release mode.didUpdateWidget()
: The Flutter framework is called when the widget is rebuiltWidget.canUpdate
To detect old and new nodes at the same location in the Widget tree, and then determine if an update is needed ifWidget.canUpdate
returntrue
This callback is called. As mentioned earlier,Widget.canUpdate
Returns true if the key and runtimeType of the new and old widgets are equal at the same timedidUpdateWidget()
Will be called.deactivate()
This callback is called when the State object is removed from the tree. In some scenarios, the Flutter Framework will reinsert the State object into the tree, such as when the subtree containing the State object moves from one place in the tree to another (this can be done via GlobalKey). If it is not reinserted into the tree after removaldispose()
Methods.dispose()
: called when the State object is permanently removed from the tree; Resources are usually released in this callback.
Figure 3-2 shows the life cycle of StatefulWidget.
3.1.8 Introduction to the built-in Component library of the Flutter SDK
Flutter provides a rich and powerful set of basic components. On top of the basic component library Flutter provides a Material style (the default Android visual style) and a Cupertino style (iOS visual style) component library. To use the base component library, you need to import:
import 'package:flutter/widgets.dart';
Copy the code
Let’s look at the common components.
Based on the component
- Text: This component lets you create formatted Text.
- Row, Column: These flexible space layout widgets let you create flexible layouts in both horizontal (Row) and vertical (Column) directions. Its design is based on the Flexbox layout model in Web development.
Stack
: Instead of linear layoutFrameLayout
Similar),Stack
Allows child widgets to be stacked that you can usePositioned
To locate them relative toStack
Up, down, left and right sides of. Stacks is based on the Absolute Positioning layout model used in Web development.- Container: Container lets you create rectangular visual elements. Containers can be decorated with a BoxDecoration, such as a background, a border, ora shadow. Containers can also have margins, padding, and constraints applied to their size. In addition, Container can use a matrix to transform it in three-dimensional space.
Material components
Flutter provides a rich set of Material components that help us build applications that follow Material Design specifications. The Material application starts with the MaterialApp component, which creates the necessary components at the root of the application, such as the Theme component, which is used to configure the Theme of the application. Whether to use MaterialApp is entirely optional, but it is a good practice to use it. In previous examples we have used several Material components such as Scaffold, AppBar, FlatButton, and so on. To use the Material component, introduce it first:
import 'package:flutter/material.dart';
Copy the code
Cupertino components
The Flutter also provides a rich Cupertino style of component, although not as rich as the Material component, but it is still in development. It is worth pointing out that there are some components in the Material component library that can change the presentation style according to the actual running platform, such as MaterialPageRoute. When switching routes, if it is an Android system, it will use the Default Android system page switching animation (bottom up); For iOS, it uses the default iOS page switch animation (from right to left). Since we didn’t have an example of the Cupertino component in the previous examples, let’s implement a simple Cupertino component style page:
// Import the Cupertino Widget library
import 'package:flutter/cupertino.dart';
class CupertinoTestRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text("Cupertino Demo"),
),
child: Center(
child: CupertinoButton(
color: CupertinoColors.activeBlue,
child: Text("Press"), onPressed: () {} ), ), ); }}Copy the code
other
-
In-depth understanding of StatelessWidget
-
abstract class StatelessWidget extends Widget Containing class: StatelessWidget A widget that does not require mutable state. A stateless widget is a widget that describes part of the user interface by building a constellation of other widgets that describe the user interface more concretely. The building process continues recursively until the description of the user interface is fully concrete (e.g., Consortium consists entirely of RenderObjectWidgets, which describe concrete RenderObjects). www.youtube.com/watch?v=wE7… Stateless widget are useful when the part of the user interface you are describing does not depend on anything other than the configuration information in the object itself and the BuildContext in which the widget is inflated. For compositions that can change dynamically, e.g. due to having an internal clock-driven state, or depending on some system state, consider using StatefulWidget. Performance considerations The build method of a stateless widget is typically only called in three situations: the first time the widget is inserted in the tree, when the widget’s parent changes its configuration, and when an InheritedWidget it depends on changes. If a widget’s parent will regularly change the widget’s configuration, or if it depends on inherited widgets that frequently change, then it is important to optimize the performance of the build method to maintain a fluid rendering performance. There are several techniques one can use to minimize the impact of rebuilding a stateless widget: Minimize the number of nodes transitively created by the build method and any widgets it creates. For example, instead of an elaborate arrangement of Rows, Columns, Paddings, and SizedBoxes to position a single child in a particularly fancy manner, consider using just an Align or a CustomSingleChildLayout. Instead of an intricate layering of multiple Containers and with Decorations to draw just the right graphical effect, consider a single CustomPaint widget. Use const widgets where possible, and provide a const constructor for the widget so that users of the widget can also do so. Consider refactoring the stateless widget into a stateful widget so that it can use some of the techniques described at StatefulWidget, such as caching common parts of subtrees and using GlobalKeys when changing the tree structure. If the widget is likely to get rebuilt frequently due to the use of InheritedWidgets, consider refactoring the stateless widget into multiple widgets, with the parts of the tree that change being pushed to the leaves. For example instead of building a tree with four widgets, the inner-most widget depending on the Theme, consider factoring out the part of the build function that builds the inner-most widget into its own widget, so that only the inner-most widget needs to be rebuilt when the theme changes. {@tool snippet} The following is a skeleton of a stateless widget subclass called GreenFrog. Normally, widgets have more constructor arguments, each of which corresponds to a final property. class GreenFrog extends StatelessWidget { const GreenFrog({ Key key }) : super(key: key);
@override Widget build(BuildContext context) { return Container(color: const Color(0xFF2DBD3A)); } } {@end-tool} {@tool snippet} This next example shows the more generic widget Frog which can be given a color and a child: class Frog extends StatelessWidget { const Frog({ Key key, this.color = const Color(0xFF2DBD3A), this.child, }) : super(key: key);
final Color color; final Widget child;
@override Widget build(BuildContext context) { return Container(color: color, child: child); } } {@end-tool} By convention, widget constructors only use named arguments. Named arguments can be marked as required using @required. Also by convention, the first argument is key, and the last argument is child, children, or the equivalent. See also: StatefulWidget and State, for widgets that can build differently several times over their lifetime. InheritedWidget, for widgets that introduce ambient state that can be read by descendant widgets.
- Mutable state widgets are not required.
- When you’re very specific about something
- Reference: www.youtube.com/watch?v=wE7…
- Stateless widgets are useful when the part of the user interface you are describing does not depend on anything other than configuration information in the object itself and the build context of the widget bloat. For combinations that can change dynamically, for example, because there is an internal clock drive state, or depending on some system state, consider using the StatefulWidget.
- Performance Considerations
- Stateless widget build methods are typically called in only three cases:
- The first time you insert the widget into the tree,
- When the parent of a widget changes its configuration,
- And when inherited widgets depend on changes.
- If the parent of the widget changes the configuration of the widget regularly, or if it relies on an inherited widget that changes frequently, it is important to optimize the performance of the build method to maintain fluid rendering performance.
- Minimize the number of nodes that can be created transitively by the build method and any widgets it creates. For example, rather than elaborate arrangements of rows, columns, padding, and size boxes, consider using Align or CustomSingleChildLayout to position a single child in a particularly fancy way. Consider a single CustomPaint widget instead of a complex layer of containers and decorations to draw the correct graphical effect.
- Use const widgets whenever possible, and provide a const constructor for the widget so that the user of the widget can do the same.
- Consider refactoring a stateless widget into a stateful one so that it can use some of the techniques described in StatefulWidget, such as caching the common parts of a subtree and using GlobalKey when changing the tree structure.
- If widgets are likely to be rebuilt frequently due to the use of inherited widgets, consider refactoring a stateless widget into multiple widgets and pushing the changed parts of the tree onto the leaves. For example, instead of building a tree of four widgets (the innermost widget depends on the theme), consider breaking down the part of the builder that builds the innermost widget into its own widget, so that only the innermost widget needs to be rebuilt when the theme changes.
-
StatelessWidget
-
InheritedWidget data sharing component
-
For example, if you share data in an InheritedWidget in your app’s root widget, you can retrieve that shared data in any of your sub-widgets! For example, the Flutter SDK uses InheritedWidget to share app Theme and Locale information.
-
Features didChangeDependencies
-
The callback method for didChangeDependencies in state is called whenever the dependencies change; Dependent behavior refers only to whether the child widget uses InheritedWidget data in the parent widget; If it does, it will be dependent, and the dependent child widget’s didChangeDependencies method will be called.
-
context.getElementForInheritedWidgetOfExactType<ShareDataWidget>().widget Copy the code
-
If it is by ‘getElementForInheritedWidgetOfExactType’
-
The dependOnInheritedWidgetOfExactType () method is replaced by the context. GetElementForInheritedWidgetOfExactType < ShareDataWidget > () the widget, Let’s take a look at the source code for these two methods (the implementation code is in the Element class, and the relationship between Context and Element is described later) :
@override InheritedElement getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() { assert(_debugCheckStateIsActiveForAncestorLookup()); final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T]; return ancestor; } @override InheritedWidget dependOnInheritedWidgetOfExactType({ Object aspect }) { assert(_debugCheckStateIsActiveForAncestorLookup()); final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T]; // If (ancestor! = null) { assert(ancestor is InheritedElement); return dependOnInheritedElement(ancestor, aspect: aspect) as T; } _hadUnsatisfiedDependencies = true; return null; }Copy the code
We can see that dependOnInheritedWidgetOfExactType than getElementForInheritedWidgetOfExactType () () more adjustable dependOnInheritedElement method, DependOnInheritedElement source code is as follows:
@override InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) { assert(ancestor ! = null); _dependencies ?? = HashSet<InheritedElement>(); _dependencies.add(ancestor); ancestor.updateDependencies(this, aspect); return ancestor.widget; }Copy the code
You can see that the dependOnInheritedElement method is primarily registered! See here is clear, call dependOnInheritedWidgetOfExactType () and getElementForInheritedWidgetOfExactType () difference is that the former will depend on registration, while the latter is not, So the call dependOnInheritedWidgetOfExactType (), InheritedWidget and rely on it and of the sons of component relationship registration is complete, when after InheritedWidget changes, will update depends on its components, That is, the didChangeDependencies() and build() methods of these descendants will be tuned. And when the call is getElementForInheritedWidgetOfExactType (), since there is no registration dependencies, so after when InheritedWidget changes, the children will not update the corresponding Widget.
Reference: book. Flutterchina. Club/chapter7 / in…