Flutter: Layout System

Flutter layout is like an assembly line — parent passes constraints, child reports size, parent determines position.

📋 Prerequisites: You need to have completed the following first

1. What You Will Learn


2. A Story from a UI Designer Transitioning to Development

(1) The Pain: The Yellow-Black Overflow Stripe Nightmare

Bob received the ShopApp UI design specs — product cards needed an image on the left and info on the right, with a price row at the bottom. He nested a Row inside a Column, only to see yellow-black striped overflow warnings across the screen. After adjusting padding and margin for ages, either text was truncated or images were distorted. 1000 products, 10 screen sizes, layout problems everywhere.

(2) The Constraint Passing Solution

Flutter's layout has one core rule: constraints pass down, sizes report up, positions are determined by the parent. Understanding this rule, Bob could predict every Widget's behavior.

DART
import 'package:flutter/material.dart';

// Parent gives tight constraints: exactly 200x100
SizedBox(
  width: 200,
  height: 100,
  child: Text('I will be 200x100'), // Text forced to 200x100
)

// Parent gives loose constraints: 0 to maxWidth
Row(
  children: [
    Expanded(child: Text('I take remaining space')),
    SizedBox(width: 80, child: Text('I am 80 wide')),
  ],
)
TEXT
> Output: Run locally with Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly across platforms.

(3) The Result: Get Layouts Right the First Time

After mastering constraint passing, Bob's first-time layout accuracy improved from 30% to 90%, and overflow warnings disappeared entirely.


3. Box Model and Constraint Passing

100%
graph TD
    subgraph Constraint Passing
        P[Parent Constraints] --> C[Child Layout]
        C --> S[Child Size]
        S --> P
    end
    subgraph Common Layouts
        R[Row] --> EX1[Expanded]
        CO[Column] --> EX2[Flexible]
        ST[Stack] --> POS[Positioned]
    end
TEXT
> Output: Run locally with Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly across platforms.

(1) Constraint Types

Type Description Example
Tight min = max, fixed size SizedBox(width: 100)
Loose min = 0, max = parent max Child inside Center
Unbounded max = infinity Column inside ListView
Bounded Both min and max are finite Column inside a Page

▶ Example

TEXT
> Output: Run locally with Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly across platforms.

: Constraint passing demonstration

DART
import 'package:flutter/material.dart';

// Demo: constraint passing in action
class ConstraintDemo extends StatelessWidget {
  const ConstraintDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          // Tight constraint: exactly 200 height
          Container(
            height: 200,
            color: Colors.blue[100],
            child: const Center(child: Text('Height: 200 (tight)')),
          ),
          // Loose constraint: takes remaining space
          Expanded(
            child: Container(
              color: Colors.green[100],
              child: const Center(child: Text('Expanded (loose, fills remaining)')),
            ),
          ),
        ],
      ),
    );
  }
}
TEXT
> Output: Run locally with Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly across platforms.

4. Linear Layout: Row and Column

(1) Alignment Properties

Property Direction Row Effect Column Effect
mainAxisAlignment Main axis Horizontal alignment Vertical alignment
crossAxisAlignment Cross axis Vertical alignment Horizontal alignment
mainAxisSize Main axis size Width Height

▶ Example

TEXT
> Output: Run locally with Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly across platforms.

: Product info row (Row)

DART
import 'package:flutter/material.dart';

// Product info row: image + details
Row(
  crossAxisAlignment: CrossAxisAlignment.start,
  children: [
    // Fixed-size image
    ClipRRect(
      borderRadius: BorderRadius.circular(8),
      child: Image.network(
        'https://cdn.shopapp.com/laptop.jpg',
        width: 100,
        height: 100,
        fit: BoxFit.cover,
      ),
    ),
    const SizedBox(width: 12),
    // Flexible details
    Expanded(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text('Pro Laptop 16"',
            style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
          const SizedBox(height: 4),
          Text('Electronics > Laptops',
            style: TextStyle(fontSize: 12, color: Colors.grey[600])),
          const SizedBox(height: 8),
          Row(
            children: [
              const Text('\$1,299.99',
                style: TextStyle(fontSize: 18, color: Colors.green)),
              const SizedBox(width: 8),
              Text('\$1,599.99',
                style: TextStyle(
                  fontSize: 14,
                  decoration: TextDecoration.lineThrough,
                  color: Colors.grey[400],
                )),
            ],
          ),
        ],
      ),
    ),
  ],
)
TEXT
> Output: Run locally with Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly across platforms.

(2) Common Alignment Values Comparison

MainAxisAlignment Effect
start Align to start (default)
center Center
end Align to end
spaceBetween First/last at edges, equal spacing between
spaceAround Equal spacing on both sides of each item
spaceEvenly All spacing equal

5. Elastic Layout: Expanded and Flexible

▶ Example

TEXT
> Output: Run locally with Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly across platforms.

: Price and button elastic layout

DART
import 'package:flutter/material.dart';

// Bottom bar: price (expanded) + button (fixed)
Container(
  padding: const EdgeInsets.all(12),
  child: Row(
    children: [
      Expanded(
        flex: 2, // Takes 2/3 of space
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          mainAxisSize: MainAxisSize.min,
          children: [
            const Text('Total', style: TextStyle(fontSize: 12)),
            Text('\$${total.toStringAsFixed(2)}',
              style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
          ],
        ),
      ),
      Expanded(
        flex: 1, // Takes 1/3 of space
        child: ElevatedButton(
          onPressed: onCheckout,
          style: ElevatedButton.styleFrom(
            padding: const EdgeInsets.symmetric(vertical: 16)),
          child: const Text('Checkout'),
        ),
      ),
    ],
  ),
)
TEXT
> Output: Run locally with Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly across platforms.
Widget Behavior Remaining Space Use Case
Expanded Force fill Distributed proportionally Must fill all space
Flexible Optionally fill Distributed proportionally, can be less Flexible but not forced

▶ Example

TEXT
> Output: Run locally with Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly across platforms.

: Expanded vs Flexible

DART
import 'package:flutter/material.dart';

Row(
  children: [
    // Expanded: must fill allocated space
    Expanded(
      flex: 1,
      child: Container(color: Colors.red, child: const Text('Expanded')),
    ),
    // Flexible: can be smaller than allocated space
    Flexible(
      flex: 1,
      child: Container(
        color: Colors.blue,
        child: const Text('Flexible'), // May not fill all space
      ),
    ),
    // Fixed width
    SizedBox(width: 80, child: Container(color: Colors.green, child: const Text('Fixed'))),
  ],
)
TEXT
> Output: Run locally with Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly across platforms.

6. Layered Layout: Stack and Positioned

Stack allows children to overlap in paint order, and Positioned controls absolute positioning.

▶ Example

TEXT
> Output: Run locally with Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly across platforms.

: Product image badges

DART
import 'package:flutter/material.dart';

// Simplified class definition
class Product {
  final String imageUrl;
  const Product({required this.imageUrl});
}

// Product image with discount badge and favorite button
Stack(
  children: [
    // Base image
    ClipRRect(
      borderRadius: BorderRadius.circular(12),
      child: Image.network(
        product.imageUrl,
        height: 200,
        width: double.infinity,
        fit: BoxFit.cover,
      ),
    ),
    // Discount badge (top-left)
    Positioned(
      top: 8,
      left: 8,
      child: Container(
        padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
        decoration: BoxDecoration(
          color: Colors.red,
          borderRadius: BorderRadius.circular(4),
        ),
        child: Text('${discountPercent}% OFF',
          style: const TextStyle(color: Colors.white, fontSize: 12)),
      ),
    ),
    // Favorite button (top-right)
    Positioned(
      top: 8,
      right: 8,
      child: CircleAvatar(
        backgroundColor: Colors.white.withOpacity(0.8),
        child: const Icon(Icons.favorite_border, size: 18),
      ),
    ),
    // Stock indicator (bottom)
    Positioned(
      bottom: 8,
      left: 8,
      child: Container(
        padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
        decoration: BoxDecoration(
          color: Colors.black54,
          borderRadius: BorderRadius.circular(4),
        ),
        child: const Text('In Stock', style: TextStyle(color: Colors.white, fontSize: 10)),
      ),
    ),
  ],
)
TEXT
> Output: Run locally with Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly across platforms.
Stack Property Description Default
alignment Alignment for non-Positioned children AlignmentDirectional.topStart
fit How children adapt StackFit.loose
clipBehavior Overflow clipping Clip.hardEdge

7. GridView Grid Layout

▶ Example

TEXT
> Output: Run locally with Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly across platforms.

: ShopApp product grid

DART
import 'package:flutter/material.dart';

// Simplified class definition
class Product {
  final int id;
  final String name;
  final double price;
  const Product({required this.id, required this.name, required this.price});
}

// Two-column product grid
GridView.count(
  crossAxisCount: 2,
  mainAxisSpacing: 8,
  crossAxisSpacing: 8,
  childAspectRatio: 0.75, // height = width / 0.75
  padding: const EdgeInsets.all(8),
  children: products.map((product) => ProductCard(
    key: ValueKey(product.id),
    product: product,
    onAddToCart: () => addToCart(product),
  )).toList(),
)

// Responsive grid with LayoutBuilder
LayoutBuilder(
  builder: (context, constraints) {
    int columns = (constraints.maxWidth / 180).floor().clamp(2, 4);
    return GridView.count(
      crossAxisCount: columns,
      mainAxisSpacing: 8,
      crossAxisSpacing: 8,
      childAspectRatio: 0.75,
      children: productWidgets,
    );
  },
)
TEXT
> Output: Run locally with Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly across platforms.
GridView Property Description
crossAxisCount Number of columns per row
mainAxisSpacing Main axis spacing
crossAxisSpacing Cross axis spacing
childAspectRatio Child item width-to-height ratio
padding Inner padding

8. Complete Example: ShopApp Home Page Layout

DART
import 'package:flutter/material.dart';

class ShopHomePage extends StatelessWidget {
  const ShopHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('ShopApp'),
        actions: [
          Stack(
            children: [
              IconButton(icon: const Icon(Icons.shopping_cart), onPressed: () {}),
              Positioned(right: 4, top: 4,
                child: CircleAvatar(radius: 8, backgroundColor: Colors.red,
                  child: const Text('5', style: TextStyle(fontSize: 10, color: Colors.white)))),
            ],
          ),
        ],
      ),
      body: CustomScrollView(
        slivers: [
          // Banner section
          SliverToBoxAdapter(
            child: Container(
              height: 180,
              color: Colors.blue[50],
              child: const Center(child: Text('Flash Sale - Up to 50% OFF',
                style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold))),
            ),
          ),
          // Category chips
          SliverToBoxAdapter(
            child: SizedBox(
              height: 50,
              child: ListView(
                scrollDirection: Axis.horizontal,
                padding: const EdgeInsets.symmetric(horizontal: 8),
                children: ['All', 'Electronics', 'Clothing', 'Books', 'Home']
                  .map((c) => Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 4),
                    child: Chip(label: Text(c)),
                  )).toList(),
              ),
            ),
          ),
          // Product grid
          SliverPadding(
            padding: const EdgeInsets.all(8),
            sliver: SliverGrid.count(
              crossAxisCount: 2,
              mainAxisSpacing: 8,
              crossAxisSpacing: 8,
              childAspectRatio: 0.7,
              children: List.generate(20, (i) => _buildProductCard(i)),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildProductCard(int index) {
    return Card(
      clipBehavior: Clip.antiAlias,
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          Expanded(
            flex: 3,
            child: Container(
              color: Colors.grey[200],
              child: const Icon(Icons.image, size: 48, color: Colors.grey),
            ),
          ),
          Expanded(
            flex: 2,
            child: Padding(
              padding: const EdgeInsets.all(8),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text('Product ${index + 1}', maxLines: 1, overflow: TextOverflow.ellipsis),
                  const Spacer(),
                  Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: [
                      Text('\$${(19.99 + index * 10).toStringAsFixed(2)}',
                        style: const TextStyle(color: Colors.green)),
                      const Icon(Icons.add_circle, size: 20),
                    ],
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}
TEXT
> Output: Run locally with Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly across platforms.

❓ FAQ

Q What should I do about yellow-black overflow stripes in a Row?
A The total width of Row children exceeds the Row's width. Wrap variable-width children with Expanded, or use Flexible for adaptive sizing.
Q What's the difference between Expanded and Flexible?
A Expanded forces the child to fill its allocated space (equivalent to Flexible(fit: FlexFit.tight)); Flexible allows the child to be smaller than its allocation (fit: FlexFit.loose).
Q How are non-Positioned children positioned in a Stack?
A They're positioned according to Stack's alignment property, defaulting to the top-left corner.
Q What's the difference between GridView.count and GridView.builder?
A count is for a small, fixed number of items; builder uses lazy loading for large data lists (use builder for million-level product lists).
Q Why does a ListView inside a Column cause an error?
A Column gives its children unbounded height constraints, and ListView can't determine its own height. Wrap the ListView with Expanded, or use shrinkWrap: true.
Q How do you calculate childAspectRatio?
A ratio = width / height. If each item is 180 wide and 240 tall, ratio = 180/240 = 0.75.

📖 Summary


📝 Exercises

  1. Basic (difficulty ⭐): Create a horizontally scrolling category tag bar (Chip + ListView horizontal direction).
  2. Intermediate (difficulty ⭐⭐): Implement a product detail page layout: top large image + middle info section + bottom price/buy button bar, using Column + Expanded.
  3. Challenge (difficulty ⭐⭐⭐): Use LayoutBuilder to implement a responsive product grid: 2 columns on phone, 3 on tablet, 4 on desktop, switching in real-time on window resize.

← Previous | Next →

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏