Skip to content

Flutter - Introduction to Layouts

In Flutter, layout is determined by composing widgets. Unlike some frameworks with separate layout files (like XML), you define your UI structure directly in Dart code using a rich set of layout widgets. Understanding how these widgets work and how to combine them is fundamental to creating well-structured and responsive user interfaces.

Core Layout Concept: Constraints Go Down, Sizes Go Up, Parent Sets Position

Section titled “Core Layout Concept: Constraints Go Down, Sizes Go Up, Parent Sets Position”

Flutter’s layout mechanism follows a simple rule:

  1. Constraints go down: A parent widget passes size constraints (minimum/maximum width and height) down to its child.
  2. Sizes go up: The child widget determines its own size within those constraints and passes that size back up to its parent.
  3. Parent sets position: The parent widget decides where to position the child within itself.

This means a widget’s size and position depend on its own properties, its parent’s constraints, and potentially the sizes of its siblings.

Layout widgets can be broadly categorized based on how many children they take:

  • Single-Child Layout Widgets: These widgets have a single child property.
  • Multi-Child Layout Widgets: These widgets have a children property, taking a List<Widget>.

These widgets wrap a single child to provide specific layout or styling features.

  • Container: A highly versatile widget for layout and decoration. It combines common painting, positioning, and sizing widgets.

    • Can set width, height, constraints.
    • Can apply padding (space inside) and margin (space outside).
    • Can be decorated with color, gradient, border, borderRadius, boxShadow via its decoration property (usually a BoxDecoration).
    • Can align its child using alignment.
    • If no child, constraints, width, or height are provided, it tries to be as big as possible. If constrained, it fits the constraints. If it has a child, it sizes itself to the child unless constrained.
    • Example:
  • Padding: Adds empty space around its child. Takes an EdgeInsets value.

  • Center: Centers its child within itself.

  • Align: Aligns its child within itself based on an Alignment value (e.g., Alignment.topRight, Alignment.centerLeft).

  • SizedBox: Creates a box with a specified size. If given a child, it forces the child to have that size. If not given a child, it creates an empty space of that size (useful for spacing in Rows/Columns).

  • AspectRatio: Attempts to size its child to a specific aspect ratio.

  • ConstrainedBox: Imposes additional constraints on its child.

  • FittedBox: Scales and positions its child within itself according to a BoxFit parameter (e.g., BoxFit.contain, BoxFit.cover).

  • Baseline: Positions its child’s baseline relative to the parent’s baseline.

  • FractionallySizedBox: Sizes its child to a fraction of the total available space.

These widgets arrange multiple children.

  • Row: Arranges its children horizontally. Children often need explicit sizing or to be wrapped in Expanded or Flexible to avoid overflow.

    • mainAxisAlignment: How children are aligned along the main (horizontal) axis (e.g., MainAxisAlignment.start, center, spaceBetween).
    • crossAxisAlignment: How children are aligned along the cross (vertical) axis (e.g., CrossAxisAlignment.start, center, stretch).
    • Example:
  • Column: Arranges its children vertically. Similar properties to Row (mainAxisAlignment is vertical, crossAxisAlignment is horizontal).

    • Example:
  • Stack: Allows children to overlap. Children are positioned relative to the stack’s box using Positioned widgets or aligned using the alignment property (default is top-left).

    • Example:
  • ListView: A scrollable list of children arranged linearly. Efficient for long lists because it only renders visible items.

    • ListView.builder is recommended for dynamic or large lists.
    • Example:
  • GridView: A scrollable 2D array of children.

    • GridView.builder is common.
    • Requires a gridDelegate (e.g., SliverGridDelegateWithFixedCrossAxisCount or SliverGridDelegateWithMaxCrossAxisExtent) to control layout.
    • Example:
  • Expanded & Flexible: Used within Row, Column, or Flex to control how children fill the available space.

    • Expanded forces its child to fill the available space along the main axis (equivalent to Flexible with fit: FlexFit.tight). Uses a flex factor to distribute space proportionally with other Expanded widgets.
    • Flexible allows its child to be smaller than the available space (fit: FlexFit.loose - default) or forces it to fill the space (fit: FlexFit.tight).
    • Example:
  • Table: Displays children in a table layout.

  • Flow: For implementing custom, complex layouts where children might reposition based on available space.

  • Wrap: Arranges children horizontally or vertically, wrapping to the next line when space runs out.

The power of Flutter layout comes from composition. You combine these basic layout widgets to create sophisticated UIs. For instance, a product detail screen might use a Column containing an Image, a Padding widget with a Row for title and price, and another Padding with Text for the description.

Start simple, identify the main layout direction (horizontal or vertical), and then nest widgets accordingly. Use tools like the Flutter Inspector (in DevTools) to visualize the widget tree and diagnose layout issues.

Let’s create a widget similar to the one in the original tutorial’s example:

import 'package:flutter/material.dart';
class Product {
final String name;
final String description;
final int price;
final String image; // Assume image name corresponds to an asset
Product(this.name, this.description, this.price, this.image);
}
class ProductBox extends StatelessWidget {
final Product item;
const ProductBox({Key? key, required this.item}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(2),
height: 140, // Increased height slightly for better spacing
child: Card(
elevation: 2.0, // Add some shadow
child: Row(
children: <Widget>[
// Ensure you have added images to pubspec.yaml assets
// Image.asset("assets/images/${item.image}", width: 100, height: 140, fit: BoxFit.cover),
// Placeholder if images aren't set up:
Container(width: 100, height: 140, color: Colors.grey[300], child: Icon(Icons.image, size: 50, color: Colors.grey[600])),
const SizedBox(width: 10), // Spacing between image and text
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 5.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround, // Distribute space
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
item.name,
style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Text(
item.description,
style: Theme.of(context).textTheme.bodySmall,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
Text(
"Price: \$${item.price}",
style: Theme.of(context).textTheme.titleSmall?.copyWith(color: Colors.green[700]),
),
],
),
),
),
],
),
),
);
}
}
// Example Usage in a ListView:
class ProductListScreen extends StatelessWidget {
final List<Product> products = [
Product("iPhone", "iPhone is the stylist phone ever", 1000, "iphone.png"),
Product("Pixel", "Pixel is the most featureful phone ever", 800, "pixel.png"),
// ... more products
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Product Listing")),
body: ListView.builder(
padding: const EdgeInsets.all(8.0), // Add padding around the list
itemCount: products.length,
itemBuilder: (context, index) {
return ProductBox(item: products[index]);
},
),
);
}
}

This example uses Container, Card, Row, Image (or placeholder), SizedBox, Expanded, Padding, and Column to structure the product item layout effectively.