Flutter (28): Alignment and relative positioning (Align)

Time: Column:Mobile & Frontend views:205

In the previous section, we discussed how, through Stack and Positioned, we can specify one or more child elements' precise offsets relative to the edges of their parent element, allowing them to overlap. However, if we simply want to adjust a child element's position within its parent, using the Align component is more straightforward.

28.1 Align

The Align component can adjust the position of its child, and it is defined as follows:

Align({
  Key key,
  this.alignment = Alignment.center,
  this.widthFactor,
  this.heightFactor,
  Widget child,
})
  • alignment: This requires a value of type AlignmentGeometry, which represents the starting position of the child component within its parent. AlignmentGeometry is an abstract class with two commonly used subclasses: Alignment and FractionalOffset, which we will explore in detail below.

  • widthFactor and heightFactor are properties used to determine the width and height of the Align component itself. They are two scaling factors that multiply the width and height of the child element, resulting in the final width and height of the Align component. If the values are null, the component will take up as much space as possible.

1. Example

Let’s look at a simple example:

Container(
  height: 120.0,
  width: 120.0,
  color: Colors.blue.shade50,
  child: Align(
    alignment: Alignment.topRight,
    child: FlutterLogo(
      size: 60,
    ),
  ),
)

The result is shown in Figure :

Flutter (28): Alignment and relative positioning (Align)

FlutterLogo is a component provided by the Flutter SDK, representing the Flutter logo. In the example above, we explicitly set the Container's width and height to 120. If we do not explicitly specify the width and height but instead set both widthFactor and heightFactor to 2, we can achieve the same effect:

Align(
  widthFactor: 2,
  heightFactor: 2,
  alignment: Alignment.topRight,
  child: FlutterLogo(
    size: 60,
  ),
),

Since the FlutterLogo has a width and height of 60, the final width and height of Align will be 2 × 60 = 120 2 \times 60 = 120 .

Moreover, we position the FlutterLogo in the top-right corner of the Container using Alignment.topRight. What is Alignment.topRight? From the source code, we can see its definition:

// Top-right corner
static const Alignment topRight = Alignment(1.0, -1.0);

This shows that it is simply an instance of Alignment. Let's introduce Alignment.

2. Alignment

Alignment inherits from AlignmentGeometry and represents a point within a rectangle. It has two properties, x and y, which represent offsets in the horizontal and vertical directions. Alignment is defined as follows:

Alignment(this.x, this.y)

The Alignment widget uses the center of the rectangle as the coordinate origin, meaning Alignment(0.0, 0.0) refers to the center. The values of x and y range from -1 to 1, representing the distance from the left to the right and from the top to the bottom of the rectangle, respectively. Thus, two horizontal (or vertical) units equal the width (or height) of the rectangle. For instance, Alignment(-1.0, -1.0) represents the top-left vertex, while Alignment(1.0, 1.0) represents the bottom-right vertex. Alignment(1.0, -1.0) represents the top-right vertex, which is Alignment.topRight. For convenience, the rectangle's origin, its four vertices, and the endpoints of its four edges are defined as static constants in the Alignment class.

The actual offset can be calculated using the following formula:

Actual Offset = (Alignment.x * (parentWidth - childWidth) / 2 + (parentWidth - childWidth) / 2,
                 Alignment.y * (parentHeight - childHeight) / 2 + (parentHeight - childHeight) / 2)

where childWidth is the width of the child element, and childHeight is the height of the child element.

Now, let’s revisit the previous example. By substituting Alignment(1.0, -1.0) into the above formula, we can find that the actual offset coordinates for the FlutterLogo are (60, 0).

Next, let’s see another example:

Align(
  widthFactor: 2,
  heightFactor: 2,
  alignment: Alignment(2, 0.0),
  child: FlutterLogo(
    size: 60,
  ),
)

We can first imagine the result. By substituting Alignment(2, 0.0) into the coordinate transformation formula, we can calculate the actual offset coordinates for the FlutterLogo as (90, 30). The actual result is shown in Figure :

Flutter (28): Alignment and relative positioning (Align)

3. FractionalOffset

FractionalOffset inherits from Alignment, and the only difference from Alignment is the origin of the coordinates! The coordinate origin of FractionalOffset is the top-left vertex of the rectangle, which is consistent with the layout system, making it easier to understand. The coordinate transformation formula for FractionalOffset is:

Actual Offset = (FractionalOffset.x * (parentWidth - childWidth), FractionalOffset.y * (parentHeight - childHeight))

Here’s an example:

Container(
  height: 120.0,
  width: 120.0,
  color: Colors.blue[50],
  child: Align(
    alignment: FractionalOffset(0.2, 0.6),
    child: FlutterLogo(
      size: 60,
    ),
  ),
)

The actual result is shown in Figure :

Flutter (28): Alignment and relative positioning (Align)

By substituting FractionalOffset(0.2, 0.6) into the coordinate transformation formula, we find that the actual offset for the FlutterLogo is (12, 36), which matches the actual result.


28.2 Comparison of Align and Stack

It is evident that both Align and Stack/Positioned can be used to specify the offset of child elements relative to their parent element, but they have two main differences:

  1. Reference Systems: The reference system for positioning is different; Stack/Positioned can reference the four vertices of the parent container's rectangle, while Align requires the alignment parameter to determine the coordinate origin. Different alignments correspond to different origins, and the final offset must be calculated using the transformation formula of the alignment.

  2. Child Elements: Stack can have multiple child elements that can stack on top of each other, while Align can only have one child element and does not support stacking.


28.3 Center Component

We have already used the Center component in previous chapters to center child elements, and now we will formally introduce it. By examining the SDK source code, we see that the Center component is defined as follows:

class Center extends Align {
  const Center({ Key? key, double widthFactor, double heightFactor, Widget? child })
    : super(key: key, widthFactor: widthFactor, heightFactor: heightFactor, child: child);
}

The Center class inherits from Align and simply lacks an alignment parameter. Since the alignment value in the Align constructor is set to Alignment.center, we can think of the Center component as an Align component with the alignment method already determined (Alignment.center).

As previously discussed, when widthFactor or heightFactor is null, the component's width and height will occupy as much space as possible. This is an important point to note. We will demonstrate it with an example:

... // Omitted unrelated code
DecoratedBox(
  decoration: BoxDecoration(color: Colors.red),
  child: Center(
    child: Text("xxx"),
  ),
),
DecoratedBox(
  decoration: BoxDecoration(color: Colors.red),
  child: Center(
    widthFactor: 1,
    heightFactor: 1,
    child: Text("xxx"),
  ),
)

The result is shown in Figure :

Flutter (28): Alignment and relative positioning (Align)


28.4 Summary

This section focused on the Align component and the two offset classes, Alignment and FractionalOffset. Readers should understand the differences between these two offset classes and their respective coordinate transformation formulas. Additionally, it is recommended that readers prioritize using FractionalOffset when precise offsets are needed, as its coordinate origin aligns with the layout system, making it easier to calculate the actual offset.

We also introduced the relationship between the Align component, Stack/Positioned, and Center, allowing readers to compare and understand them.

Furthermore, readers familiar with web development may notice that the features of the Align component are very similar to relative positioning (position: relative) in web development. Indeed! In most cases, we can use the Align component to achieve the same effect as relative positioning in web development, which readers can remember for comparison.