For example, a component is displayed in the upper left corner, lower right corner, and middle corner of a large component. You can set the alignment attribute in Container to achieve alignment. Flutter also has a special layout component to achieve the same effect, Align.

In this post, I’m going to cover all aspects of the Align component, from south to north.

Align Component introduction

The Align component has two main functions: it aligns its internal child nodes and resizes itself based on the size of the child nodes.

It works like this:

For example, if you want the child to be in the bottomRight corner, you can set the attribute to align.bottomright, but only if Align itself is larger than the child.

For alignment, at least the size of the outer ring should be greater than the size of the child node. If the two are the same size, they must overlap. So if we look at the outer circle which is the size of Align, what is the default? It’s going to be as big as it can be, and what constraint does the parent put on Align, so Align takes the maximum constraint.

There are, of course, non-default rules:

If there is no constraint and widthFactor and heightFactor are not set, then the size of Align is the size of the child node.

If widthFactor and heightFactor are set, then the size of Align is the child node’s size and the factor’s operand. For example, if widthFactor is 2.0, Align is twice the width of the child node.

So now that we know what Alin is, let’s look at the properties of Align.

The Align attribute

attribute type role
key Key? Component identification
alignment AlignmentGeometry The alignment method of child nodes is usually set toAlignment
widthFactor double? Width factor, the width of Align = the width of the child node * width factor
heightFactor double? Height factor, height of Align = height of child node * height factor
child Widget? Child nodes to be aligned

Alignment aligned

This attribute is used to control Alignment. Although it is of type AlignmentGeometry, Alignment is often assigned.

Alignment uses the constructor’s X and y to control position, in the range -1 to 1. If x is -1, the child is to the left of the Align, 1 means the child is to the right of the Align, and y -1 means the child is above the Align, and 1 means the child is below the Align. We will describe the specific calculation process below.

widthFactorThe width of the factor

If set to non-null, then the width of Align is the product of the child’s width and its factors. This value cannot be negative.

heightFactorHigh factor

If set to non-null, then the height of Align is the product of the height of the child node and its factors. This value cannot be negative.

With these properties in mind, let’s use effects.

Use the Align

The outer circle of Align is a Container with a black border, and the child nodes are blue blocks of 60 by 60

The basic use

The basic code is as follows:

Container(
  width: 300,
  height: 300,
  decoration: BoxDecoration(border: Border.all(color: Colors.black)),
  child: Align(
    child: Container(
      height: 60,
      width: 60,
      color: Colors.blue,
    ),
  ),
)
Copy the code

Align’s layout is centered by default and its aligment property can be adjusted

Align principle

Old rules 👉 three trees final chapter, Align is a render component, its render object is RenderPositionedBox, so let’s go to RenderPositionedBox to see how the placement works.

ParentData Data that the parent node needs to know

Before introducing placement, we will introduce the concept of ParentData, which is the data that the parent render object wants to know. For example, the position of the byte point, and so on, corresponds to the parentData property of the RenderObject in the code.

RenderObject ParentData is divided into two categories: box data and Sliver data. Let’s use ContainerParentDataMixin as an example.

mixin ContainerParentDataMixin<ChildType extends RenderObject> on ParentData {
  
  ChildType? previousSibling;
  
  ChildType? nextSibling;
  
  @override
  void detach() {
    super.detach(); }}Copy the code

From this data model, the parent node knows who the nodes before and after a child node are. ChildType is a stereotype inherited from RenderObject.

ParentData is a subtype of ContainerParentDataMixin in the render objects of the familiar Row/Column, Stack, and Wrap components. So their render objects can place their children in sequence when they are laid out.

RenderPositionedBox’s parentData is BoxParentData. BoxParentData is defined as follows:

class BoxParentData extends ParentData {
  /// The offset at which to paint the child in the parent's coordinate system.
  Offset offset = Offset.zero;

  @override
  String toString() => 'offset=$offset';
}
Copy the code

Offset is the position of the Align byte in Align, and as you can see here, alignment is the calculation of that value. Now let’s look at the calculation.

The layout process

Placement is the measurement and drawing of layout. This corresponds to performLayout and Paint

The layout process is as follows:

  • First: determine the layout of the child node, determine the size of the child node
  • Second: determine your size based on the scale factor and the size of the child nodes
  • Third: Determine the position of the child node based on the alignment attribute
  • Fourth: draw according to position

Let’s look at the process from the code below:

@override void performLayout() { final BoxConstraints constraints = this.constraints; final bool shrinkWrapWidth = _widthFactor ! = null || constraints.maxWidth == double.infinity; final bool shrinkWrapHeight = _heightFactor ! = null || constraints.maxHeight == double.infinity; if (child ! = null) { child! .layout(constraints.loosen(), parentUsesSize: true); Constrain (size (Constrain) shrinkWrapWidth? child! .size.width * (_widthFactor ?? 1.0) : Double. Infinity, shrinkWrapHeight? child! .size.height * (_heightFactor ?? 1.0) : double infinity,)); // Second alignChild(); } else {size = constraints. Constrain (size (Constrain wrapWidth? 0.0: Double. Infinity, shrinkWrapHeight? 0.0: double infinity,)); }}Copy the code

Let’s look at the code in the first place. The first place is the layout child node, which has very interesting inputs: constraints. Loosen () and parentUsesSize.

Its effect is that the constraints on the children are loose: 0 – Align has the maximum width, so the maximum available space for children of Align does not exceed Align. Truncation! There will be no overflow prompt like over.

ParentUsesSize tells the framework that the size of Align depends on my child, and if my child is marked dirty, please take me with you.

So the code in the first place is to determine the size of the child node, and the code in the second place is to determine the size of the child node. Let’s take width as an example:

The title The width factor is not null The width factor is null
The constraint is infinite trueChild node width * width factor trueChild node width
Constraint co., LTD. trueChild node width * width factor falseUnlimited width (maximum width)

The size of Align is the product of the size factor of the child node.

As long as it is not set, it is either the width of the child or the width of the parent layout, thus implementing the alignment of the child within its grandparent. Align is just a bridge.

Let’s do the third one, align the child nodes.

@protected
void alignChild() {
  _resolve();
  finalBoxParentData childParentData = child! .parentData!asBoxParentData; childParentData.offset = _resolvedAlignment! .alongOffset(size - child! .sizeas Offset);
}

Offset alongOffset(Offset other) {
  final double centerX = other.dx / 2.0;
  final double centerY = other.dy / 2.0;
  return Offset(centerX + x * centerX, centerY + y * centerY);
}

Copy the code

The alignment determines the offset we talked about above, and the way to determine the offset is alongOffset.

Can we just think about where we’re going for a second?

The process of Flutter is centered first, then subtracted left and added right. The range of addition and subtraction is the absolute value of x and y in the Alignment method.

For example, the width of Align is 0 — 120, and the width of child is 60.

So the middle position is (120-60) / 2 = 30, and the range of child is 30-90.

We are looking at the calculation of upper-left Alignment topLeft = Alignment(-1.0, -1.0).

So first of all, I’m going to define the center 30 and then I’m going to define the width centerX plus x times centerX, which is 30 plus minus 1 times 30 is 0 and then finally, the x coordinate is 0

That’s why the animation at the beginning of the article has a range of -1 to 1, -1 represents the left and top, 1 represents the right and bottom, and other values float in this range.

This is the calculation of the position offset, and what is the use of this offset?

Press this position to draw and respond to gestures!!

Drawing process

RenderPositionedBox does not have a paint method to draw. The paint method is in its parent, RenderShiftedBox.

@override
void paint(PaintingContext context, Offset offset) {
  if(child ! =null) {
    finalBoxParentData childParentData = child! .parentData!as BoxParentData;
    context.paintChild(child!, childParentData.offset + offset);
  }
}
Copy the code

We can see that the offset calculated in the cloth process is added on the basis of Align.

For example, the offset calculated in the above layout process is (20,20), then its real position is the coordinate of the upper left corner of the Align, and it is offset to the right and down (20,20). The offset is the actual drawing coordinate of the Align child node.

Gesture response range

We know that gestures have testing scope, usually within the scope of components, and also increase whether they fall on their own components. As follows:

bool hitTest(BoxHitTestResult result, { required Offset position }) {
  if(_size! .contains(position)) {if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
      result.add(BoxHitTestEntry(this, position));
      return true; }}return false;
}
Copy the code

So this is the general way to do it, if you’re in the scope of your component, you’re going to continue to determine whether you’re hitTestChildren, whether you have an extra judgment hitTestSelf.

Let’s look at the hitTestChildren of the Align render object.

@override bool hitTestChildren(BoxHitTestResult result, { required Offset position }) { if (child ! = null) { final BoxParentData childParentData = child! .parentData! as BoxParentData; Return the result. AddWithPaintOffset (offset: childParentData. Offset, / / first place position: the position, hitTest: (BoxHitTestResult result, Offset transformed) { assert(transformed == position - childParentData.offset); return child! .hitTest(result, position: transformed); }); } return false; }Copy the code

We see that when checking whether our child nodes satisfy the click, we have added an offset transformation, whose coordinates are the offset of the layout phase.

Through the above drawing and click detection, we can clearly understand the role of ParentData, which carries data for the Align component.

conclusion

This is the end of this article, which is the first article on layout components. The usage scenario, basic attributes and basic usage of Align are introduced. On the basis of these, it explores the principle that Align can achieve alignment, which is condensed into one sentence: find the middle first and calculate the factor. The Center component we use is a subclass of Align, just with the alignment property set to Center.