Layout Algorithm
Understanding how Shaft’s layout system works under the hood.
The Rule
Shaft uses the same layout algorithm as Flutter, based on one simple rule:
Constraints go down. Sizes go up. Parent sets position.
This means:
- A widget gets constraints from its parent
- The widget decides its size within those constraints
- The widget tells its parent what size it chose
- The parent decides where to position the widget
What are Constraints?
A constraint is simply four numbers:
- Minimum width and maximum width
- Minimum height and maximum height
BoxConstraints(
minWidth: 100,
maxWidth: 400,
minHeight: 50,
maxHeight: 200
)
A widget can be any size between the minimum and maximum.
Types of Constraints
Tight Constraints
When min equals max, the widget must be exactly that size.
BoxConstraints.tight(Size(100, 100))
// minWidth = maxWidth = 100
// minHeight = maxHeight = 100
Example: The root widget of your app gets tight constraints matching the screen size.
Loose Constraints
When min is zero, the widget can be as small as zero or as large as max.
BoxConstraints.loose(Size(400, 600))
// minWidth = 0, maxWidth = 400
// minHeight = 0, maxHeight = 600
Example: Center
gives loose constraints to its child, allowing it to be smaller.
Unbounded Constraints
When max is infinity, the widget can be any size.
BoxConstraints(
minWidth: 0,
maxWidth: .infinity, // Unbounded!
minHeight: 0,
maxHeight: 200
)
Example: A Column
inside a vertical ScrollView
gets unbounded height.
Tight vs Loose: A Summary
Scenario | Constraint Type | Widget Behavior |
---|---|---|
Root of app | Tight | Must match screen size |
Inside Center | Loose | Can be smaller |
Inside Expanded | Tight | Must fill allocated space |
Inside ScrollView | Unbounded | Can be any size |
Inside Column (height) | Unbounded | Natural size |
Inside SizedBox(100, 100) | Tight | Must be 100×100 |
Implementing Custom Layout
If you’re building custom layout widgets, you’ll work with RenderObject
:
class RenderCustom: RenderBox {
override func performLayout() {
// 1. Receive constraints from parent
let incoming = constraints
// 2. Layout children with new constraints
child.layout(
BoxConstraints(
minWidth: 0,
maxWidth: incoming.maxWidth,
minHeight: 0,
maxHeight: incoming.maxHeight / 2
),
parentUsesSize: true
)
// 3. Decide own size
size = Size(
width: incoming.maxWidth,
height: child.size.height * 2
)
}
override func paint(context: PaintingContext, offset: Offset) {
// 4. Position and paint child
context.paintChild(child, offset: Offset(0, 10))
}
}
Learn More
- For practical layout usage, see Layout System
- For widget styling, see Widget Styling
- Flutter’s detailed explanation: Understanding constraints