Education
Software Development Executive - II
Last updated onSep 15, 2023
Last updated onAug 28, 2023
In our journey of app development with Flutter, understanding the layout mechanism becomes crucial. It is the basis of how we arrange our widgets on the screen and define our app's UI. One key concept in this layout mechanism is 'Constraints'. To efficiently layout our widgets in a Flutter app, we need to understand the concept of 'constraints in Flutter'.
So, what exactly are these constraints? In Flutter, each widget is bound by some limitations in terms of Height and Width. These are referred to as ‘constraints’. While laying out the widgets, each parent widget gives its child widget a set of constraints to adhere to. Nevertheless, it is worth noting that Flutter's layout mechanism is vastly different from its HTML counterpart.
Demystifying Flutter constraints requires to imbibe a fundamental rule:
In other words, the parent widget passes down constraints to its child widgets. These child widgets then decide their sizes within their respective constraints and pass them back up to their parent. Lastly, the parent widget sets their children's positions on the screen.
1 //Flutter constraints example 2 Container( 3 width: 100, 4 height: 100, 5 child: Center(), 6 ) 7
This 'Flutter constraints' example demonstrates how the Container (parent) passes the constraints (width and height) to the Center (child). Here, the child widget is allowed to occupy a maximum width and height of 100 pixels, but it decides to fill lesser space considering its positioning.
Let's take a more in-depth look at how Flutter handles constraints. The process involves several steps:
Firstly, a widget asks its parent about the constraints it must adhere to. These constraints encompass a minimum and maximum width and height.
In the next step, the widget iteratively passes these constraints to its children, potentially modifying them if necessary. Upon receiving the constraints, each child widget decides its own size.
The parent widget then places its children one by one on the screen. While doing so, it respects the size preferences of the children and positions them both horizontally (along the x-axis) and vertically (along the y-axis).
In the final step, the parent widget expresses its size to its own parent, taking the original constraints into account.
1 //Flutter constraints example 2 Container( 3 width: 200, 4 height: 200, 5 child: Column( 6 children: <Widget> [ 7 Container( 8 width: 100, 9 height: 100, 10 color: Colors.red, 11 ), 12 Container( 13 width: 50, 14 height: 50, 15 color: Colors.green, 16 ), 17 ], 18 ), 19 ) 20
In this 'Flutter constraints' example, the overall Container widget (parent) envelops two child Container widgets and sets the positioning of children after considering their sizes. The red box has a width and height of 100 each, while the green box settles for a width and height of 50 each. Both respect the overall constraints of width 200 and height 200 passed down by the parent widget.
To further quench our thirst for knowledge about 'constraints in Flutter', let’s employ examples. Visual learning always cements the understanding of concepts. So, let's walk through the negotiation that happens in a typical scenario where a widget wants to layout its two children.
1 //Flutter BoxConstraints example 2 ConstrainedBox( 3 constraints: BoxConstraints( 4 minWidth: 70, 5 minHeight: 70, 6 maxWidth: 150, 7 maxHeight: 150, 8 ), 9 child: Container( 10 color: Colors.red, 11 width: 1000, 12 height: 1000, 13 ), 14 ) 15
In this 'Flutter BoxConstraints example', the ConstrainedBox (parent widget) defines a set of constraints for its child Container. The ConstrainedBox tells the Container that it must have a width and height of anywhere between 70 and 150 pixels. It cannot go beyond these limits. Despite the Container desiring to be 1000 pixels in width and height, it must respect the constraints and settle for the maximum permissible size of 150 pixels each.
While Flutter's layout engine is efficient, a few limitations result from its one-pass layout process.
In Flutter, every widget is rendered by underlying RenderBox objects. These boxes dictate how the widget handles constraints and aligns its children. We generally encounter three kinds of boxes when dealing with constraints:
These boxes try to occupy as much space as they can. Center and ListView widgets are prime examples of this kind.
Such boxes size themselves to be as large as their children. Transform and Opacity widgets follow this behavior.
These boxes aim to maintain a specific size, like Image and Text widgets. They do not adjust their size based on the child widget or parent widget.
It is critical to understand how different widgets handle constraints in Flutter. Widgets like Container, Row, and Column have specific behaviors and rules for managing constraints.
Let's take the Container widget, one of the most commonly used widgets in Flutter. By default, it tries to be as big as possible. However, if you provide it width or height, it tries to honor that and be that specific size.
1 //Flutter BoxConstraints example with container widget 2 Container( 3 width: 200, 4 height: 200, 5 color: Colors.red, 6 ) 7
In this 'Flutter BoxConstraints example', the Container widget is constrained to be 200 pixels wide and 200 pixels high. It doesn't matter how much space is available to the widget; it will stick to the provided size unless constraints from its parent object force it to update.
While Row and Column widgets, also known as flex boxes, have their behavior determined based on the constraints they receive. They behave differently depending on whether the constraints are bounded or unbounded, which we will explore more in later sections.
Flutter constraints can be broadly categorized into two types based on the flexibility they offer - Tight and Loose constraints.
Tight constraints are essentially bindings that provide one exact size to follow. They have their minimum width equal to the maximum width, and the minimum height equal to the maximum height. For instance, an App widget, which is encapsulated by the RenderView class, is given a constraint to exactly fill the application's content area, typically the whole screen.
1 //Flutter BoxConstraints example with tight constraint 2 BoxConstraints.tight(Size(100.0, 100.0)) 3
In this 'Flutter BoxConstraints example', BoxConstraints.tight provides a constraint that has an exact size of 100 pixels in width and height, contributing towards tight constraints.
On the other hand, Loose constraints have a minimum of zero and a maximum non-zero. These are the constraints that a parent widget provides to its child when it allows the child to decide its size. For instance, the Center allows its child to be any size it wants, up to the screen size. It imposes only the maximum width and the maximum height constraints, which too equal to the parent size.
1 //Flutter BoxConstraints example with loose constraint 2 Center( 3 child: Container( 4 child: const Text('I am centered.'), 5 ), 6 ) 7
In this 'Flutter BoxConstraints example', the Center widget allows the Container to define its own width and height within the loose constraints.
While dealing with constraints in Flutter, another type that comes into play is unbounded or infinite constraints. These come into effect when either the maximum width or the maximum height is set to double.infinity.
A certain category of widgets that attempt to be as large as possible, wouldn't be able to function well given an unbounded constraint, and in debug mode, it throws an exception. The most common scenario where you encounter unbounded constraint is within flex boxes, such as Row or Column, and within a scrollable regime, like ListView and other ScrollView subclasses.
Consider this Flutter box constraints example:
1 Container( 2 color: Colors.red, 3 width: double.infinity, 4 height: 100, 5 ) 6
In this 'Flutter BoxConstraints example', the Container widget attempts to take up infinite width, leading to an unbounded width constraint.
It's crucial to effectively manage unbounded constraints to avoid "overflow warning". For instance, when a child widget fails to comply with the constraints from its parent, the parent widget can choose to wrap it with a Flexible widget. This flexibility solves the overflow issues by adjusting the size of the child widget to fit within the available space.
1 Flexible( 2 child: Container( 3 color: Colors.red, 4 width: double.infinity, 5 height: 100, 6 ), 7 ) 8
In order to develop robust and responsive layouts, it's vital to understand and handle unbounded constraints effectively.
Flexibility plays a key role in managing space distribution among children along the main axis. In the world of 'constraints in Flutter', Row and Column widgets reflect this concept, known as flex boxes.
A flex box behaves differently based on whether its constraint is bounded or unbounded along the main axis. In bounded constraints, it tries to be as spacious as possible while in unbounded constraints, it sizes itself to fit its children.
Furthermore, a flex box's cross direction (width for Column and height for Row) must never be unbounded, as it doesn’t make sense for a flex box to have unbounded constraints, because it wouldn't know how to size and align its children along that axis.
Let's demonstrate how flex affects 'Flutter constraints' with this final example:
1 Row( 2 children: [ 3 Expanded( 4 flex: 3, 5 child: Container(color: Colors.red), 6 ), 7 Expanded( 8 flex: 2, 9 child: Container(color: Colors.blue), 10 ), 11 ], 12 ) 13
In this 'Flutter constraints' example, the Row widget has two child Container widgets. The Expanded widget wraps both containers with a given flex factor. This factor determines how much space they should occupy in the Row. The red container takes up more space than the blue one as it has a larger flex factor.
Through this deep exploration, we've decoded the concept of 'constraints in Flutter'. We began with understanding the basic philosophy behind Flutter constraints, surveying their influence on widgets, and their categories: tight, loose, and unbounded. We dived into examples to understand how different widgets handle these constraints and uncovered the pivotal role of the flex property in managing space distribution among widgets inside a flex container (Row or Column).
This understanding forms the foundation to handle the alignment, sizing, and positioning of widgets in complex Flutter layouts efficiently. As we develop our Flutter applications, keeping these constraints principles in mind will ensure a responsive and crisp UI/UX!
Tired of manually designing screens, coding on weekends, and technical debt? Let DhiWise handle it for you!
You can build an e-commerce store, healthcare app, portfolio, blogging website, social media or admin panel right away. Use our library of 40+ pre-built free templates to create your first application using DhiWise.