I have learned Fullter for some time and written many demos. I am familiar with the use of some commonly used widgets. However, I always feel that I have no general understanding of the framework of Flutter. For example, when you write a UI, you don’t know why the container gets smaller when you add a child; The purpose of a key in a widget, although officially explained, is a little hard to understand in a nutshell. Therefore, a deeper understanding of the Flutter framework is necessary.
build
The first building
The main method of the entry to flutter calls the runApp(Widget App) method directly. The app parameter is the Widget of our root view. We follow the runApp method directly
Void runApp (Widget app) {WidgetsFlutterBinding. The ensureInitialized () / / this method is to do some necessary initialization. The framework of flutter. attachRootWidget(app) .. scheduleWarmUpFrame(); }Copy the code
RunApp method calls the first WidgetsFlutterBinding. The ensureInitialized () method, this method is to do some necessary initialization
class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding { static WidgetsBinding ensureInitialized() { if (WidgetsBinding.instance == null) WidgetsFlutterBinding(); return WidgetsBinding.instance; }}Copy the code
WidgetsFlutterBinding is mixed in with a number of other bindings
- BindingBase The base class of those single-service mixins
- GestureBinding A binding of the GestureBinding framework’s gesture subsystem that handles user input events
- The ServicesBinding accepts platform messages and converts them into binary messages for the platform to communicate with flutter
- SchedulerBinding scheduling system, Call Transient Callbacks (window. onBeginFrame callback), Persistent callbacks (window. onDrawFrame callback), and post-frame Callbacks (only called once at the end of a Frame, removed by the system after the call, executed after Persistent callbacks before the window.ondrawFrame callback returns)
- PaintingBinding A binding for the drawing library that handles image caching
- The SemanticsBinding semantic layer Bridges the Flutter engine, mainly providing low-level support for ancillary functions
- RendererBinding Bridges the rendering tree with the Flutter engine
- The WidgetsBinding Widget layer Bridges the Flutter engine
The above is the main point of these Binding, here is no need to do too much, WidgetsFlutterBinding. The ensureInitialized () returns the WidgetsBinding object, We then immediately call the attachRootWidget(app) method of the WidgetsBinding, threading the Widget object of our root view, and continue with the attachRootWidget method
void attachRootWidget(Widget rootWidget) {
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget
).attachToRenderTree(buildOwner, renderViewElement);
}
Copy the code
Creates a RenderObjectToWidgetAdapter, let attachToRenderTree method, call it directly after BuildOwner is Widget framework management class
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [RenderObjectToWidgetElement<T> element]) { if (element == null) { owner.lockState(() { element = createElement(); assert(element ! = null); element.assignOwner(owner); }); owner.buildScope(element, () { element.mount(null, null); }); } else { element._newWidget = this; element.markNeedsBuild(); } return element; }Copy the code
Element is empty, the owner locked first, and then call the RenderObjectToWidgetAdapter the createElement method () returns the RenderObjectToWidgetElement object, After assigning owner to Element (assignOwner method), owner calls the buildScope method
void buildScope(Element context, [VoidCallback callback]) { if (callback == null && _dirtyElements.isEmpty) return; Timeline.startSync('Build', arguments: timelineWhitelistArguments); try { _scheduledFlushDirtyElements = true; if (callback ! = null) { _dirtyElementsNeedsResorting = false; try { callback(); } finally {} } ... }Copy the code
Omitted parts and subsequent code, can see buildScope method first calls the callback (is element. The mount (null, null) method), go back to the mount RenderObjectToWidgetElement method
@override
void mount(Element parent, dynamic newSlot) {
assert(parent == null);
super.mount(parent, newSlot);
_rebuild();
}
Copy the code
Super. mount(parent, newSlot) calls RootRenderObjectElement mount(null). The mount method in RenderObjectElement is called up
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_renderObject = widget.createRenderObject(this);
attachRenderObject(newSlot);
_dirty = false;
}
Copy the code
The mount method in RenderObjectElement calls Element’s mount method
@mustCallSuper void mount(Element parent, dynamic newSlot) { _parent = parent; _slot = newSlot; _depth = _parent ! = null ? _parent.depth + 1 : 1; _active = true; if (parent ! = null) // Only assign ownership if the parent is non-null _owner = parent.owner; if (widget.key is GlobalKey) { final GlobalKey key = widget.key; key._register(this); } _updateInheritance(); }Copy the code
Element method of the mount is actually made some assignment, to confirm the current Element in the tree, then back to the mount of RenderObjectElement method, call the widget. The createRenderObject (this) method, Widget is RenderObjectToWidgetAdapter instance, it returns the RenderObjectWithChildMixin object, then call attachRenderObject method
@override void attachRenderObject(dynamic newSlot) { assert(_ancestorRenderObjectElement == null); _slot = newSlot; _ancestorRenderObjectElement = _findAncestorRenderObjectElement(); / / get the RenderObjectElement RenderObjectElement object _ancestorRenderObjectElement recently? .insertChildRenderObject(renderObject, newSlot); RenderObjectElement Final ParentDataElement<RenderObjectWidget> ParentDataElement = _findAncestorParentDataElement(); if (parentDataElement ! = null) _updateParentData(parentDataElement.widget); } / / / the insertChildRenderObject RenderObjectToWidgetElement method, Void insertChildRenderObject(RenderObject child, dynamic slot) { assert(slot == _rootChildSlot); assert(renderObject.debugValidateChild(child)); renderObject.child = child; }Copy the code
RenderObjectElement mount method creates the RenderObject object and inserts it into the render tree. Then go back to RenderObjectToWidgetElement method, the mount called after _rebuild () method, _rebuild () method of the main is to call the Element updateChild method
@protected Element updateChild(Element child, Widget newWidget, Dynamic newSlot) {if (newWidget == null) {// Deactivate the child if (child! = null) deactivateChild(child); return null; } if (child ! If (child.widget == newWidget) {//Widget does not change if (child.slot! UpdateSlotForChild (child, newSlot); // Update child slot return child; // Return child} if (widget.canupdate (child.widget, newWidget)) {// If the Widget has not changed, then check whether the Widget can be updated. If so, repeat the previous step if (child.slot! = newSlot) updateSlotForChild(child, newSlot); child.update(newWidget); return child; } deactivateChild(child); // If you can't update the child deactivate, Then create a new Element in the inflateWidget(newWidget, newSlot)} return inflateWidget(newWidget, newSlot); // Create a new Element based on the Widget object and slot}Copy the code
Since we are building for the first time and the child is null, we go directly to the inflateWidget method to create a new Element object and follow up with the inflateWidget method
@protected Element inflatinflateWidgeteWidget(Widget newWidget, dynamic newSlot) { final Key key = newWidget.key; If (key is GlobalKey) {// The key of the newWidget is GlobalKey final Element newChild = _retakeInactiveElement(key, newWidget); // Reuse Element if (newChild! = null) { newChild._activateWithParent(this, newSlot); // Activate this Element (insert newChild into the Element tree). Final Element updatedChild = updateChild(newChild, newWidget, newSlot); Return updatedChild; // Return updated Element}} Final Element newChild = newWidget.createElement(); // Call createElement() to create newChild.mount(this, newSlot); // Continue to call newChild Element's mount method (so that the recursion continues, and when the recursion completes, the build process ends) return newChild; // Return child Element}Copy the code
An inflateWidget is a Widget that gets an Element object and then calls the child Element’s mount.
StatelessElement, StatefulElement, StatelessElement, StatelessElement, StatefulElement, StatelessElement, StatelessElement, StatefulElement, StatelessElement, StatelessElement, StatefulElement, StatelessElement, StatefulElement
@override void mount(Element parent, dynamic newSlot) { super.mount(parent, newSlot); _firstBuild(); } void _firstBuild() { rebuild(); //Element's rebuild method is usually called in three places. When BuildOwner. ScheduleBuildFor called tag when this Element is dirty / / 2. Void rebuild() {if (!); void rebuild() {if (! _active || ! _dirty) return; performRebuild(); Override void performRebuild() {Widget built; try { built = build(); // Build Widget (StatelessElement calls build method directly, StatefulElement calls state.build directly)} catch (e, stack) { built = ErrorWidget.builder(_debugReportException('building $this', e, stack)); // Create an ErrorWidget} finally {_dirty = false; } try { _child = updateChild(_child, built, slot); Catch (e, stack) {built = errorWidget. builder(_debugReportException('building $this', e, stack)); _child = updateChild(null, built, slot); }}Copy the code
Take a look MultiChildRenderObjectElement mount method
@override void mount(Element parent, dynamic newSlot) { super.mount(parent, newSlot); _children = List<Element>(widget.children.length); Element previousChild; for (int i = 0; i < _children.length; i += 1) { final Element newChild = inflateWidget(widget.children[i], previousChild); // Directly inflate creates a new Element according to the Widget. _children[I] = newChild; previousChild = newChild; }}Copy the code
As you can see, different elements are constructed in different ways. The main method of mounting an Element is to determine the current position of the Element in the whole tree and insert it. The mount method for ComponentElement (layer 2) builds the Widget tree and then updates recursively (including reuse, updating, and directly creating the Element tree). The mount method in RenderObjectElement (level 2) creates the RenderObject object and inserts it into the render tree. MultiChildRenderObjectElement (RenderObjectElement subclass) in RenderObjectElement will continue to create children Element.
Conclusion: First is founded by WidgetBinding RenderObjectToWidgetAdapter then call it attachToRenderTree method, created the RenderObjectToWidgetElement object, Mount _rebuild, mount _rebuild, and then call updateChild. The updateChild method will recursively update the Element tree. If the child does not have one, create a new Element. Mount it into the Element tree (if RenderobjectElement is used, the mount process creates the RenderObject and inserts it into the RenderTree).
The build is triggered by setState
The setState method is called callback and then the markNeedsBuild method of the Element object of the State is called. MarkNeedsBuild marks Element as dirty and adds it to the dirty list via BuildOwner and calls the onBuildScheduled callback (set when WidgetsBinding is initialized, It goes back to the window.scheduleFrame method, so that the onBeginFrame and onDrawFrame callbacks (which are set when SchedulerBinding is initialized and perform some callback) of the post-window will be called. SchedulerBinding calls the buildScope method in BuildOwner via persisterCallbacks. We only looked at part of buildScope above, and buildScope’s callBack is null when an interface redraw is triggered by the setState method
void buildScope(Element context, [VoidCallback callback]) { if (callback == null && _dirtyElements.isEmpty) return; Timeline.startSync('Build', arguments: timelineWhitelistArguments); try { _scheduledFlushDirtyElements = true; if (callback ! = null) { Element debugPreviousBuildTarget; _dirtyElementsNeedsResorting = false; try { callback(); // Call callback} finally {}} _dirtyElements. Sort (element._sort); _dirtyElementsNeedsResorting = false; int dirtyCount = _dirtyElements.length; int index = 0; while (index < dirtyCount) { try { _dirtyElements[index].rebuild(); // Iterate over dirtyElements and execute their rebuild method to rebuild these elements} catch (e, stack) {} index += 1; if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting) { _dirtyElements.sort(Element._sort); _dirtyElementsNeedsResorting = false; dirtyCount = _dirtyElements.length; while (index > 0 && _dirtyElements[index - 1].dirty) { index -= 1; }}}} finally {for (Element Element in _dirtyElements) {// And clear dirtyElements assert(element._indirtyList); element._inDirtyList = false; } _dirtyElements.clear(); _scheduledFlushDirtyElements = false; _dirtyElementsNeedsResorting = null; Timeline.finishSync(); }}Copy the code
The obvious thing is to iterate over the elements in dirtyElements and rebuild them.
layout
Window uses the scheduleFrame method to make the SchedulerBinding execute the handleBeginFrame method (perform transientCallbacks) and the handleDrawFrame method (perform persistentCallb) Acks, postFrameCallbacks), added _handlePersistentFrameCallback when RendererBinding initialization, it calls the core drawFrame drawing method.
@protected void drawFrame() { assert(renderView ! = null); pipelineOwner.flushLayout(); / / layout pipelineOwner flushCompositingBits (); // Refresh the dirty renderObject data pipelineowner.flushpaint (); / / draw renderView.com positeFrame (); . / / the binary data sent to the GPU pipelineOwner flushSemantics (); // send the semantics to the system}Copy the code
FlushLayout triggers the layout, and the dirty nodes of the RenderObject tree are laid out one by one by calling the performLayout method. Let’s look at RenderPadding
@override void performLayout() { _resolve(); // Parse the padding argument if (child == null) {// If no child, Size = constraint.constrain (size (_resolvedpStunning. left + _resolvedpStunning. right, _resolvedPadding.top + _resolvedPadding.bottom )); return; } final BoxConstraints innerConstraints = constraints.deflate(_resolvedPadding); // Subtract the padding to generate a new constraint innerConstraints child.layout(innerConstraints, parentUsesSize: true); Child Final BoxParentData childParentData = child.parentData; childParentData.offset = Offset(_resolvedPadding.left, _resolvedPadding.top); // Set the offset value of childParentData (this value is drawn offset from parent, Constrain (size (// Combine constraints with the padding and child's Sieze to find your size _resolvedPadding.left + child.size.width + _resolvedPadding.right, _resolvedPadding.top + child.size.height + _resolvedPadding.bottom )); }Copy the code
You can see that there are two types of layouts in RenderPadding. If there is no child, use the constraints passed by the parent and the padding to determine the size. Otherwise, lay out the child, set its drawing offset, and determine its size using the constraints passed by the parent, the padding, and the size of the child. RenderPadding is typically a single child RenderBox, so let’s look at a RenderBox with multiple children. For example RenderFlow
@override void performLayout() { size = _getSize(constraints); Int I = 0; _randomAccessChildren.clear(); RenderBox child = firstChild; while (child ! = null) {// iterate over children _randomAccesschildren.add (child); final BoxConstraints innerConstraints = _delegate.getConstraintsForChild(i, constraints); // Get the constraint for child. This method is abstract child.layout(innerConstraints, parentUsesSize: true); // Final FlowParentData childParentData = child.parentData; childParentData.offset = Offset.zero; child = childParentData.nextSibling; i += 1; }}Copy the code
It can be seen that the size of RenderFlow is directly determined according to the constraints, and the children are not laid out first. Therefore, the size of RenderFlow is independent of the children, and each child is laid out in turn.
There is also a typical treetop type of RenderBox, the LeafRenderObjectWidget subclass creates RenderObject objects, they have no children, they are the objects that need to be rendered, for example
@override
void performLayout() {
size = _sizeForConstraints(constraints);
}
Copy the code
It’s very simple to determine your size by constraint and you’re done. So the performLayout process is two points, determine your size and layout your child. All the constraints we have mentioned above are subclasses of RenderBox. These Constraints are implemented through BoxConstraints, but RenderSliver subclasses are implemented through SliverConstraints. Although they constrain the child differently, they all need to perform the same operations during the layout process.
draw
Once the layout is complete, PipelineOwner is drawn using flushPaint
void flushPaint() { try { final List<RenderObject> dirtyNodes = _nodesNeedingPaint; _nodesNeedingPaint = <RenderObject>[]; // Sort the list of dirtyNodes with the deepest in the first for (RenderObject node in dirtyNodes.. sort((RenderObject a, RenderObject b) => b.depth - a.depth)) { assert(node._layer ! = null); if (node._needsPaint && node.owner == this) { if (node._layer.attached) { PaintingContext.repaintCompositedChild(node); } else { node._skippedPaintingOnLayer(); } } } } finally {} }Copy the code
PaintingContext. RepaintCompositedChild (node) will be called to the child. The _paintWithContext (childContext, Offset. Zero) method, and then call to child paint method, Let’s take a look at the first drawing. The dirty node should be RenderView, followed by RenderView’s paint method
@override void paint(PaintingContext context, Offset offset) { if (child ! = null) context.paintChild(child, offset); // Draw child}Copy the code
I don’t have anything to draw myself, so I’ll just draw a Child, and then RenderShiftedBox, okay
@override void paint(PaintingContext context, Offset offset) { if (child ! = null) { final BoxParentData childParentData = child.parentData; context.paintChild(child, childParentData.offset + offset); // Draw child}}Copy the code
It looks like we’re recursively drawing a Child without drawing anything, so let’s look at RenderDecoratedBox
Override void paint(PaintingContext context, Offset Offset) {override void paint(PaintingContext context, Offset Offset) {override void paint(PaintingContext context, Offset Offset) {override void paint(PaintingContext context, Offset Offset) { This field is BoxParentData or the following instance _painter?? = _decoration.createBoxPainter(markNeedsPaint); // Obtain painter final ImageConfiguration filledConfiguration = configuration. CopyWith (size: size); // Obtain Painter final ImageConfiguration filledConfiguration = configuration. If (position = = DecorationPosition. Background) {/ / painting background _painter. Paint (context. Canvas, offset, filledConfiguration); If (decoration.iscomplex) context.setiscomplexHint (); } super.paint(context, offset); / / the child, In direct call paintChild if (position = = DecorationPosition. Foreground) {/ / painting prospect _painter. Paint (context. Canvas, offset, filledConfiguration); if (decoration.isComplex) context.setIsComplexHint(); }}Copy the code
The implementation in the paint method should include drawing itself and drawing a child if you have one, or just drawing your own content if you have no children, look at RenderImage
@override void paint(PaintingContext context, Offset offset) { if (_image == null) return; _resolve(); Canvas: context.canvas, rect: offset & size, Image: _image, scale: _scale, colorFilter: _colorFilter, fit: _fit, alignment: _resolvedAlignment, centerSlice: _centerSlice, repeat: _repeat, flipHorizontally: _flipHorizontally, invertColors: invertColors, filterQuality: _filterQuality ); }Copy the code
So basically the process of drawing is that if you have something to draw yourself, the implementation in paint should include drawing yourself and drawing a child. If you don’t have a child, just draw your own content, which is a simpler process.
The above is some summary of their own learning, if there are mistakes, please point out, we discuss together, feel good, a praise!