当前位置:网站首页>57. The core principle of flutter - layout process
57. The core principle of flutter - layout process
2022-06-27 11:53:00 【Wind and rain 「 83 」】
Layout (Layout) The process
Layout( Layout ) The process is mainly to determine the layout information of each component ( Size and location ),Flutter The layout process is as follows :
- The parent node passes the constraint to the child node (constraints) Information , Limit the maximum and minimum width and height of child nodes .
- The child nodes determine their own size according to the constraint information (size).
- The parent node is based on specific layout rules ( Different layout components have different layout algorithms ) Determine the position of each child node in the layout space of the parent node , Using offset offset Express .
- The whole process of recursion , Determine the size and location of each node .
You can see , The size of the component is determined by itself , The location of the component is determined by the parent component .
Flutter There are many layout class components in , It can be divided into single sub component and multi sub component according to the number of children , Now let's have a deeper understanding by customizing a single sub component and multiple sub components respectively Flutter The layout process of , Then I will talk about the layout update process and Flutter Medium Constraints( constraint ).
Example of single subcomponent layout (CustomCenter)
We implement a single subcomponent CustomCenter, Basic functions and Center Component alignment , Through this example, we will demonstrate the main process of layout .
First , We define components , To show the principle , We do not use composition to implement components , But directly through customization RenderObject The way to achieve . Because the middle component needs to contain a child node , So we inherit directly SingleChildRenderObjectWidget.
class CustomCenter extends SingleChildRenderObjectWidget {
const CustomCenter2({Key? key, required Widget child})
: super(key: key, child: child);
@override
RenderObject createRenderObject(BuildContext context) {
return RenderCustomCenter();
}
}
Then implement RenderCustomCenter. Here, direct inheritance RenderObject Will be a little closer to the bottom , But this requires us to manually implement something that has nothing to do with the layout , Such as event distribution and other logic . To focus more on the layout itself , We choose to inherit from RenderShiftedBox, It will help us realize some functions beyond the layout , So we just need to rewrite performLayout
, In this function, you can implement the child node centering algorithm .
class RenderCustomCenter extends RenderShiftedBox {
RenderCustomCenter({RenderBox? child}) : super(child);
@override
void performLayout() {
//1. Start with the sub components layout, Then get its size
child!.layout(
constraints.loosen(), // Pass constraints to child nodes
parentUsesSize: true, // Because we're going to use child Of size, So it can't be for false
);
//2. Determine the size of itself according to the size of sub components
size = constraints.constrain(Size(
constraints.maxWidth == double.infinity
? child!.size.width
: double.infinity,
constraints.maxHeight == double.infinity
? child!.size.height
: double.infinity,
));
// 3. According to the size of the child node of the parent node , Calculate the offset of the child node after it is centered in the parent node , Then save the offset in
// The child nodes of the parentData in , In the subsequent drawing stage , use .
BoxParentData parentData = child!.parentData as BoxParentData;
parentData.offset = ((size - child!.size) as Offset) / 2;
}
}
Please refer to the notes for the layout process , Additional instructions are required here 3 spot :
When laying out child nodes , constraints
yes CustomCenter The constraint information passed by the parent component of , The constraint information we pass to the child nodes is constraints.loosen()
, So let's see loosen Implementation of the source code :
BoxConstraints loosen() {
return BoxConstraints(
minWidth: 0.0,
maxWidth: maxWidth,
minHeight: 0.0,
maxHeight: maxHeight,
);
}
Obviously ,CustomCenter Constrain the maximum width and height of child nodes not to exceed their own maximum width and height .
The child node is at the parent node (CustomCenter) Under the constraint of , Determine your width and height ; here CustomCenter It will determine its own width and height according to the width and height of child nodes , The logic of the above code is , If CustomCenter When the maximum width height constraint passed to it by the parent node is infinite , Its width and height will be set to the width and height of its child nodes . Be careful , If at this time CustomCenter If the width and height of is set to infinity, there will be a problem , Because if your width and height are infinite within an infinite range , What is the actual width and height , Its parent node will be confused ! The screen size is fixed , This is obviously unreasonable . If CustomCenter The maximum width height constraint passed to it by the parent node is not infinite , Then you can specify your own width and height as infinite , Because in a limited space , If a child node says it is infinite , The maximum is the size of the parent node . therefore , In short ,CustomCenter Will try to fill the space of the parent element .
CustomCenter After you determine your own size and the size of the child nodes, you can determine the location of the child nodes , According to the centering algorithm , Calculate the origin coordinates of the child nodes and save them in the... Of the child nodes parentData in , It will be used in the subsequent drawing stage , How to use it , Let's see RenderShiftedBox In the default paint Realization :
@override void paint(PaintingContext context, Offset offset) { if (child != null) { final BoxParentData childParentData = child!.parentData! as BoxParentData; // from child.parentData Get the offset of the child node relative to the current node , Add the offset of the current node in the screen , // Is the offset of the child node in the screen . context.paintChild(child!, childParentData.offset + offset); } }
performLayout technological process
You can see , The logical reality of the layout performLayout Method implementation , Let's summarize performLayout What to do in :
- If there are subcomponents , Recursively layout the sub components .
- Determine the size of the current component (size), It usually depends on the size of the subcomponents .
- Determines the starting offset of the subcomponent in the current component .
stay Flutter In component library , There are some common single sub components, such as Align、SizedBox、DecoratedBox etc. , You can open the source code to see its implementation .
Let's take a look at an example of multiple subcomponents .
Example of multi subcomponent layout (LeftRightBox)
In actual development, we often use the welt left - Right layout , Now let's achieve a LeftRightBox Component to implement the left - Right layout , because LeftRightBox Have two children , Use one Widget Array to hold subcomponents .
First we define the components , Unlike single subcomponents, multiple subcomponents need to inherit from MultiChildRenderObjectWidget:
class LeftRightBox extends MultiChildRenderObjectWidget {
LeftRightBox({
Key? key,
required List<Widget> children,
}) : assert(children.length == 2, " Only two children"),
super(key: key, children: children);
@override
RenderObject createRenderObject(BuildContext context) {
return RenderLeftRight();
}
}
Next, we need to implement RenderLeftRight, In its performLayout We implement left - Right layout algorithm :
class LeftRightParentData extends ContainerBoxParentData<RenderBox> {}
class RenderLeftRight extends RenderBox
with
ContainerRenderObjectMixin<RenderBox, LeftRightParentData>,
RenderBoxContainerDefaultsMixin<RenderBox, LeftRightParentData> {
// Initialize each child Of parentData
@override
void setupParentData(RenderBox child) {
if (child.parentData is! LeftRightParentData)
child.parentData = LeftRightParentData();
}
@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
RenderBox leftChild = firstChild!;
LeftRightParentData childParentData =
leftChild.parentData! as LeftRightParentData;
RenderBox rightChild = childParentData.nextSibling!;
// We limit the width of the right child to no more than half of the total width
rightChild.layout(
constraints.copyWith(maxWidth: constraints.maxWidth / 2),
parentUsesSize: true,
);
// Adjust the... Of the right child node offset
childParentData = rightChild.parentData! as LeftRightParentData;
childParentData.offset = Offset(
constraints.maxWidth - rightChild.size.width,
0,
);
// layout left child
// Of the left child node offset The default is (0,0), To ensure that the left child node is always displayed , We don't modify it offset
leftChild.layout(
// The maximum width left
constraints.copyWith(
maxWidth: constraints.maxWidth - rightChild.size.width,
),
parentUsesSize: true,
);
// Set up LeftRight Self size
size = Size(
constraints.maxWidth,
max(leftChild.size.height, rightChild.size.height),
);
}
@override
void paint(PaintingContext context, Offset offset) {
defaultPaint(context, offset);
}
@override
bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
return defaultHitTestChildren(result, position: position);
}
}
You can see , The actual layout process is not much different from the single child node , However, multiple child components need to layout multiple child nodes at the same time . And RenderCustomCenter The difference is ,RenderLeftRight It is directly inherited from RenderBox, At the same time ContainerRenderObjectMixin and RenderBoxContainerDefaultsMixin Two mixin, these two items. mixin Help us achieve the default rendering and event processing logic .
About ParentData
In the above two examples, we are implementing the corresponding RenderObject The child nodes are used parentData object ( Add the child node's offset Information is stored in ), You can see parentData Although it belongs to child Properties of , But it starts from setting ( Include initialization ) To use are in the parent node , That's why it's called “parentData”, actually Flutter This attribute in the framework is mainly used to layout Stage is designed to save its own layout information .
in addition ,“ParentData Save node layout information ” It's just an agreement , When defining components, we can save the layout information of child nodes anywhere , You can also save non layout information . however , It is strongly recommended that you follow Flutter The specification of , In this way, our code will be easier for others to understand , It will also be easier to maintain .
Layout update
Theoretically , After the layout of a component changes , It will affect the layout of other components , So when the layout of some components changes , The stupidest way is to use the whole component tree relayout( Rearrange )! But for all components relayout The cost is still too big , So we need to explore ways to reduce relayout Cost plan . actually , In some specific situations , After the component changes, we only need to rearrange some components ( And not the whole tree relayout ).
Layout boundary (relayoutBoundary)
Suppose there is a component tree structure of a page as shown in the figure :
If Text3 The length of the text changes , Will lead to Text4 The location and Column2 The size of will also change ; Again because Column2 The parent component of SizedBox The size has been limited , therefore SizedBox The size and position of the will not change . So eventually we need to do relayout The components of are :Text3、Column2, Here we need to pay attention to :
- Text4 There is no need to rearrange , because Text4 The size of has not changed , It's just a change of position , And its location is in the parent component Column2 Determined during layout .
- It's easy to see : If Text3 and Column2 There are other components between , These components also need relayout Of .
In this case ,Column2 Namely Text3 Of relayoutBoundary ( Rearranged boundary nodes ). Of each component renderObject One of them _relayoutBoundary
Property points to its own layout , If the current node layout changes , Oneself to _relayoutBoundary
All nodes on the path need relayout.
that , Whether a component is relayoutBoundary What are the conditions for ? Here is a principle and four scenarios , The principle is “ Changes in the size of the component itself do not affect the parent component ”, If a component satisfies one of the following four conditions , Then it is relayoutBoundary :
The size of the parent component of the current component does not depend on the size of the current component ; In this case, the parent component will call the child component layout function during layout and pass a parentUsesSize Parameters , The parameter is false The layout algorithm of the parent component does not depend on the size of the child component .
The size of the component depends only on the constraints passed by the parent component , It does not depend on the size of the descendant components . In this way, the size change of the descendant component will not affect its own size , In this case, the sizedByParent Property must be true( We'll talk about it later ).
The constraint passed by the parent component to itself is a strict constraint ( Fixed width and height , Here can speak ); In this case, even if the size of itself depends on the descendant elements , But it will not affect the parent component .
The component is the root component ;Flutter The root component of the application is RenderView, Its default size is the current device screen size
The corresponding code implementation is :
if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) {
_relayoutBoundary = this;
} else {
_relayoutBoundary = (parent! as RenderObject)._relayoutBoundary;
}
In the code if The judgment conditions in and above 4 strip One-to-one correspondence , Except for the second condition ( sizedByParent by true ), Everything else is very intuitive , We will introduce the second condition later .
markNeedsLayout
When the component layout changes , It needs to call markNeedsLayout
Method to update the layout , It has two main functions :
- Connect yourself to its relayoutBoundary All nodes on the path are marked as “ Need layout ” .
- Request new frame; In the new frame Will be marked as “ Need layout ” The nodes of are rearranged .
Let's look at the core source code :
void markNeedsLayout() {
_needsLayout = true;
if (_relayoutBoundary != this) { // If it is a layout boundary node
markParentNeedsLayout(); // Recursively marks all nodes from the current node to its layout boundary node as
} else {// If it is a layout boundary node
if (owner != null) {
// Add layout boundary nodes to piplineOwner._nodesNeedingLayout In the list
owner!._nodesNeedingLayout.add(this);
owner!.requestVisualUpdate();// This function will eventually request a new frame
}
}
}
flushLayout()
markNeedsLayout After execution , It will be relayoutBoundary Add nodes to piplineOwner._nodesNeedingLayout
In the list , Then ask for a new frame, new frame When it comes, it will execute piplineOwner.drawFrame
Method :
void drawFrame() {
pipelineOwner.flushLayout(); // Rearrange
pipelineOwner.flushCompositingBits();
pipelineOwner.flushPaint();
...
}
flushLayout() Will be added to _nodesNeedingLayout
Rearrange the nodes in , Let's take a look at its core source code :
void flushLayout() {
while (_nodesNeedingLayout.isNotEmpty) {
final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
_nodesNeedingLayout = <RenderObject>[];
// Sort the nodes from small to large according to their depth in the tree, and then re layout
for (final RenderObject node in dirtyNodes..sort((a,b) => a.depth - b.depth)) {
if (node._needsLayout && node.owner == this)
node._layoutWithoutResize(); // Rearrange
}
}
}
to glance at _layoutWithoutResize
Realization
void _layoutWithoutResize() {
performLayout(); // Rearrange ; Will recursively layout descendant nodes
_needsLayout = false;
markNeedsPaint(); // After the layout is updated ,UI It also needs to be updated
}
Layout technological process
If the component has sub components , It's in performLayout You need to call the subcomponent in layout Method layout the sub components first , Let's see layout The core process of :
void layout(Constraints constraints, { bool parentUsesSize = false }) {
RenderObject? relayoutBoundary;
// First determine the layout boundary of the current component
if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) {
relayoutBoundary = this;
} else {
relayoutBoundary = (parent! as RenderObject)._relayoutBoundary;
}
// _needsLayout Indicates whether the current component is marked as requiring layout
// _constraints It is the constraint passed by the parent component to the current component during the last layout
// _relayoutBoundary Is the layout boundary of the current component at the time of the last layout
// therefore , When the current component is not marked for re layout , And the constraints passed by the parent component have not changed ,
// If the layout boundary has not changed, there is no need to re layout , Just go back .
if (!_needsLayout && constraints == _constraints && relayoutBoundary == _relayoutBoundary) {
return;
}
// If layout is required , Cache constraints and layout boundaries
_constraints = constraints;
_relayoutBoundary = relayoutBoundary;
// I'll explain later
if (sizedByParent) {
performResize();
}
// Execution layout
performLayout();
// After the layout is finished, the _needsLayout Set as false
_needsLayout = false;
// Mark the current component as needing redrawing ( Because after the layout changes , Need to redraw )
markNeedsPaint();
}
In short, the layout process is divided into the following steps :
Determine the layout boundary of the current component .
Determine if you need to rearrange , If it is not necessary, it will return directly , On the contrary, it needs to be rearranged . When the layout is not required, three conditions must be met at the same time :
The current component is not marked for re layout .
The constraints passed by the parent component have not changed .
When the layout boundary of the current component has not changed .
call performLayout() Make a layout , because performLayout() Will call the subcomponent in layout Method , So this is a recursive process , After the recursion, the layout of the entire component tree is completed .
Request redraw .
sizedByParent
stay layout In the method , The logic is as follows :
if (sizedByParent) {
performResize(); // Resize components
}
As we said above sizedByParent by true It means : The size of the current component depends only on the constraints passed by the parent component , It does not depend on the size of the descendant components . We said earlier ,performLayout Determining the size of the current component in usually depends on the size of the child components , If sizedByParent by true, The size of the current component does not depend on the size of the sub component , For clarity of logic ,Flutter It is agreed in the framework that , When sizedByParent by true when , The logic to determine the current component size should be extracted to performResize() in , In this case performLayout There are only two main tasks : Layout the sub components and determine the layout start position offset of the sub components in the current component .
Let's go through a AccurateSizedBox Example to demonstrate sizedByParent by true How should we lay out :
AccurateSizedBox
Flutter Medium SizedBox The component passes the constraints of its parent component to its child components , That means , If the parent component limits the minimum width to 100, Even if we pass SizedBox Specify a width of 50, It doesn't work either , because SizedBox The implementation of will make SizedBox The subcomponents of the must first satisfy SizedBox Constraints of parent components . Remember when we wanted to AppBar Medium limit loading An example of component size :
AppBar(
title: Text(title),
actions: <Widget>[
SizedBox( // Try to use SizedBox customized loading Wide and high
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 3,
valueColor: AlwaysStoppedAnimation(Colors.white70),
),
)
],
)
The actual results are shown in the figure :
The reason why it does not take effect , Because the parent component limits the minimum height , Of course we can use it UnconstrainedBox + SizedBox To achieve the effect we want , But here we hope that we can do it through a component , For this purpose, we customize a AccurateSizedBox Components , It and SizedBox The main difference is AccurateSizedBox Itself will comply with the constraints passed by its parent component Instead of letting its subcomponents meet AccurateSizedBox Constraints of parent components , Specifically :
- AccurateSizedBox Its size only depends on the constraints of the parent component and the width and height specified by the user .
- AccurateSizedBox After determining its own size , Limit the size of its subcomponents .
class AccurateSizedBox extends SingleChildRenderObjectWidget {
const AccurateSizedBox({
Key? key,
this.width = 0,
this.height = 0,
required Widget child,
}) : super(key: key, child: child);
final double width;
final double height;
@override
RenderObject createRenderObject(BuildContext context) {
return RenderAccurateSizedBox(width, height);
}
@override
void updateRenderObject(context, RenderAccurateSizedBox renderObject) {
renderObject
..width = width
..height = height;
}
}
class RenderAccurateSizedBox extends RenderProxyBoxWithHitTestBehavior {
RenderAccurateSizedBox(this.width, this.height);
double width;
double height;
// The size of the current component depends only on the constraints passed by the parent component
@override
bool get sizedByParent => true;
// performResize Will call
@override
Size computeDryLayout(BoxConstraints constraints) {
// Set current element width and height , Obey the constraints of the parent component
return constraints.constrain(Size(width, height));
}
// @override
// void performResize() {
// // default behavior for subclasses that have sizedByParent = true
// size = computeDryLayout(constraints);
// assert(size.isFinite);
// }
@override
void performLayout() {
child!.layout(
BoxConstraints.tight(
Size(min(size.width, width), min(size.height, height))),
// The parent container is a fixed size , Changing the size of a child element does not affect the parent element
// parentUseSize by false when , The layout boundary of the subcomponent will be itself , The current component will not be affected after the sub component layout changes
parentUsesSize: false,
);
}
}
There are three points to note in the above code :
- our RenderAccurateSizedBox No longer inherit directly from RenderBox, It's inherited from RenderProxyBoxWithHitTestBehavior,RenderProxyBoxWithHitTestBehavior Is indirectly inherited from RenderBox Of , It contains the default hit test and drawing related logic , After inheriting it, we don't need to implement it manually .
- We moved the logic to determine the current component size to computeDryLayout In the method , because RenderBox Of performResize Method will call computeDryLayout , And take the returned result as the size of the current component . according to Flutter Framework agreement , We should rewrite computeDryLayout Method, not performResize Method , Just like in layout, we should rewrite performLayout Method, not layout Method ; however , It's just an agreement , It's not compulsory , But we should try our best to abide by this agreement , Unless you know exactly what you're doing and make sure that the people who will later maintain your code know .
- RenderAccurateSizedBox When calling a sub component layout when , take
parentUsesSize
Set asfalse
, In this way, the subcomponent will become a layout boundary .
AfterLayout
AfterLayout You can get the proxy rendering objects of the sub components after the layout is completed (RenderAfterLayout), RenderAfterLayout Objects render objects on behalf of sub components , therefore , adopt RenderAfterLayout Object can also get the attributes on the sub component rendering object , Such as piece size 、 Location, etc .
AfterLayout The implementation code of is as follows :
class AfterLayout extends SingleChildRenderObjectWidget {
AfterLayout({
Key? key,
required this.callback,
Widget? child,
}) : super(key: key, child: child);
@override
RenderObject createRenderObject(BuildContext context) {
return RenderAfterLayout(callback);
}
@override
void updateRenderObject(
BuildContext context, RenderAfterLayout renderObject) {
renderObject..callback = callback;
}
/// When the component tree layout is finished, it will be triggered , Be careful , It is not triggered after the current component layout
final ValueSetter<RenderAfterLayout> callback;
}
class RenderAfterLayout extends RenderProxyBox {
RenderAfterLayout(this.callback);
ValueSetter<RenderAfterLayout> callback;
@override
void performLayout() {
super.performLayout();
// You cannot call back directly callback, The reason is that after the current component layout is completed, other components may not be completed
// If callback It triggers UI to update ( For example, call setState) May be an error . therefore , We
// stay frame At the end, trigger the callback .
SchedulerBinding.instance!
.addPostFrameCallback((timeStamp) => callback(this));
}
/// Coordinates of the starting point of the component in the screen coordinates ( The offset )
Offset get offset => localToGlobal(Offset.zero);
/// The rectangular space area occupied by the component on the screen
Rect get rect => offset & size;
}
There are three points to note in the above code :
callback The call time is not immediately after the sub component completes the layout , The reason is that after the sub component layout is completed, other components may not be completed , If called at this time callback, once callback There is code in that triggers the update ( For example, call setState) May be an error . So we have frame At the end, trigger the callback .
RenderAfterLayout The parent class of the layout call of RenderProxyBox Of performLayout():
void performLayout() { if (child != null) { child!.layout(constraints, parentUsesSize: true); size = child!.size; } else { size = computeSizeForNoChild(constraints); } }
You can see that the constraints passed directly from the parent component to itself are passed to the child components , And set the size of the sub component to its own size . in other words RenderAfterLayout Is the same size as its subcomponents
We defined offset and rect Two attributes , They are the position offset of the component relative to the screen and the rectangular space occupied . But in practice , We often need to obtain the coordinates and rectangular space range of the child component relative to a parent component , At this point we can call RenderObject Of
localToGlobal
Method , For example, the following code shows Stack A subcomponent in gets the relative value of Stack Rectangular space range of :... Widget build(context){ return Stack( alignment: AlignmentDirectional.topCenter, children: [ AfterLayout( callback: (renderAfterLayout){ // What we need to get is AfterLayout Subcomponents are relative to Stack Of Rect _rect = renderAfterLayout.localToGlobal( Offset.zero, // find Stack Corresponding RenderObject object ancestor: context.findRenderObject(), ) & renderAfterLayout.size; }, child: Text('[email protected]'), ), ] ); }
Further discussion Constraints
Constraints( constraint ) It mainly describes the minimum and maximum width and height limits , Understanding how a component determines the size of itself or child nodes according to constraints in the layout process is very helpful for us to understand the layout behavior of components , Now let's go through an implementation 200*200 Red Container To illustrate . In order to eliminate interference , Let's make the root node (RenderView) As Container The parent component of , Our code is :
Container(width: 200, height: 200, color: Colors.red)
But after the actual operation , You will find that the whole screen turns red ! Why? ? Let's see. RenderView Layout implementation of :
@override
void performLayout() {
//configuration.size For the current device screen
_size = configuration.size;
if (child != null)
child!.layout(BoxConstraints.tight(_size)); // Force subcomponents to be as large as the screen
}
Here we need to introduce two common constraints :
- Loose constraints : The minimum width and height are not limited ( by 0), Only the maximum width and height are limited , Can pass
BoxConstraints.loose(Size size)
To quickly create . - Strictly restrict : Limit to fixed size ; That is, the minimum width is equal to the maximum width , The minimum height is equal to the maximum height , Can pass
BoxConstraints.tight(Size size)
To quickly create .
You can find ,RenderView A strict constraint is passed to the subcomponent in , That is, force the sub component size to be equal to the screen size , therefore Container It fills the screen . Then how can we make the specified size effective ? The standard answer is Introduce an intermediate component , Make this intermediate component comply with the constraints of the parent component , Then pass the new constraints to the sub components . For this example , The simplest way is to use a Align Package components Container:
@override
Widget build(BuildContext context) {
var container = Container(width: 200, height: 200, color: Colors.red);
return Align(
child: container,
alignment: Alignment.topLeft,
);
}
Align Will abide by RenderView Constraints , Let yourself fill the screen , A loose constraint is then passed to the subcomponent ( The minimum width and height is 0, The maximum width and height is 200), such Container It can become 200 * 200 了 .
Of course, we can use other components instead Align, such as UnconstrainedBox, But the principle is the same , Readers can check the source code validation .
summary
Adoption of this section , Believe you are right flutter I am familiar with the layout process of , Now let's take a look at flutter Map of the official website
Now let's take a look at the official website about Flutter The explanation of the layout :
“ In the layout ,Flutter Will DFS( Depth-first traversal ) Traversing the rendering tree , and Limit in a top-down way Pass from parent node to child node . If child nodes want to determine their own size , be must Follow the restrictions passed by the parent node . The child node responds within the constraints established by the parent node Size in a bottom-up manner Pass to parent node .”
边栏推荐
- QStyle类用法总结(三)
- R language uses the poisgof function of epidisplay package to test the goodness of fit of Poisson regression and whether there is overdispersion
- 2022ciscn central China Web
- Fork/Join 框架基本使用和原理
- 政策关注 | 加快构建数据基础制度,维护国家数据安全
- 【TcaplusDB知识库】TcaplusDB单据受理-创建业务介绍
- Jianmu continuous integration platform v2.5.0 release
- Jerry's adding timer interrupt [chapter]
- Xuri 3sdb, installing the original ROS
- 杰理之无缝循环播放【篇】
猜你喜欢
【TcaplusDB知识库】TcaplusDB单据受理-建表审批介绍
星际争霸的虫王IA退役2年搞AI,自叹不如了
15+城市道路要素分割应用,用这一个分割模型就够了!
Redis 分布式锁15问,看看你都掌握了哪些?
QStyle类用法总结(三)
alibaba jarslink
[tcapulusdb knowledge base] Introduction to tcapulusdb general documents
Usage of rxjs mergemap
What is the TCP 3-time handshake process?
优博讯出席OpenHarmony技术日,全新打造下一代安全支付终端
随机推荐
Leetcode 177 The nth highest salary (June 26, 2022)
StarCraft's Bug King ia retired for 2 years to engage in AI, and lamented that it was inferior
内存四区(栈,堆,全局,代码区)
Build the Internet of things system from scratch
QStyle类用法总结(三)
R语言使用epiDisplay包的dotplot函数通过点图的形式可视化不同区间数据点的频率、使用by参数指定分组参数可视化不同分组的点图分布、使用dot.col参数指定分组数据点的颜色
Four memory areas (stack, heap, global, code area)
Peak store app imitation station development play mode explanation source code sharing
Interviewer: with the for loop, why do you need foreach?
干货!零售业智能化管理会遇到哪些问题?看懂这篇文章就够了
等等, 怎么使用 SetMemoryLimit?
巅峰小店APP仿站开发玩法模式讲解源码分享
Jerry added an input capture channel [chapter]
【TcaplusDB知识库】TcaplusDB系统管理介绍
The DBSCAN function of FPC package in R language performs density clustering analysis on data, and the plot function visualizes the clustering graph
Wechat applet realizes five-star evaluation
【TcaplusDB知识库】TcaplusDB-tcapsvrmgr工具介绍(一)
Qstype implementation of self drawing interface project practice (I)
21: Chapter 3: develop pass service: 4: further improve [send SMS, interface]; (in [send SMS, interface], call Alibaba cloud SMS service and redis service; a design idea: basecontroller;)
[tcapulusdb knowledge base] tcapulusdb doc acceptance - create business introduction