Layout & Styling

Flutter layout system — padding, alignment, constraints, theming, and responsive design

Flutter uses a constraint-based layout system. Understanding how constraints flow down and sizes flow up is key.

Padding and margin

// Padding widget
Padding(
  padding: EdgeInsets.all(16),            // all sides
  child: Text('Hello'),
)
Padding(
  padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
  child: Text('Hello'),
)
Padding(
  padding: EdgeInsets.only(left: 8, top: 4),
  child: Text('Hello'),
)

// Margin via Container
Container(
  margin: EdgeInsets.all(8),
  child: Text('Hello'),
)

Alignment and Centering

// Center a child
Center(child: Text('Centered'))

// Align within a specific position
Align(
  alignment: Alignment.topLeft,
  child: Text('Top Left'),
)
Align(
  alignment: Alignment.bottomRight,
  child: Text('Bottom Right'),
)

Flex, Expanded, and Spacer

// Expanded — fill available space
Row(
  children: [
    Expanded(flex: 1, child: Container(color: Colors.red)),
    Expanded(flex: 2, child: Container(color: Colors.blue)),  // twice as wide
  ],
)

// Flexible — can shrink or expand
Row(
  children: [
    Flexible(child: Text('This text can shrink')),
    Expanded(child: Container(color: Colors.grey)),
  ],
)

// Spacer — empty space in flex layouts
Row(
  children: [
    Text('Left'),
    Spacer(),
    Text('Right'),
  ],
)

SizedBox and ConstrainedBox

// Fixed size
SizedBox(width: 100, height: 50, child: Container(color: Colors.blue))

// Just spacing (no child)
SizedBox(height: 16)  // vertical gap
SizedBox(width: 8)    // horizontal gap

// Constrain child
ConstrainedBox(
  constraints: BoxConstraints(
    minWidth: 100,
    maxWidth: 300,
    minHeight: 50,
  ),
  child: Text('Constrained'),
)

Theme and styling

// Define a theme for the whole app
MaterialApp(
  theme: ThemeData(
    colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
    textTheme: TextTheme(
      headlineLarge: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
      bodyLarge: TextStyle(fontSize: 16),
      labelSmall: TextStyle(fontSize: 11, letterSpacing: 0.5),
    ),
    cardTheme: CardTheme(
      elevation: 2,
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
    ),
    elevatedButtonTheme: ElevatedButtonThemeData(
      style: ElevatedButton.styleFrom(
        padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
      ),
    ),
  ),
  home: HomeScreen(),
);

Access theme in widgets

// Read theme values
final theme = Theme.of(context);
Text(
  'Styled text',
  style: theme.textTheme.headlineLarge?.copyWith(color: theme.colorScheme.primary),
);

// Use Material 3 color roles
Container(color: theme.colorScheme.surface);
Container(color: theme.colorScheme.surfaceVariant);
Text('Error', style: TextStyle(color: theme.colorScheme.error));
ElevatedButton(style: ElevatedButton.styleFrom(
  backgroundColor: theme.colorScheme.primary,
  foregroundColor: theme.colorScheme.onPrimary,
));

Responsive design

MediaQuery — device info

final size = MediaQuery.of(context).size;
final width = size.width;
final height = size.height;
final padding = MediaQuery.of(context).padding; // safe area insets
final orientation = MediaQuery.of(context).orientation; // portrait / landscape

LayoutBuilder — responsive layouts

LayoutBuilder(
  builder: (context, constraints) {
    if (constraints.maxWidth > 600) {
      return Row(children: [sidebar, Expanded(child: content)]);
    } else {
      return Column(children: [content]);
    }
  },
)

Breakpoint pattern

enum ScreenType { mobile, tablet, desktop }

ScreenType getScreenType(double width) {
  if (width > 1200) return ScreenType.desktop;
  if (width > 600) return ScreenType.tablet;
  return ScreenType.mobile;
}

SafeArea and system UI

// Avoid notches and system overlays
SafeArea(
  child: Column(children: [...]),
)

// SliverAppBar for scrollable app bars
CustomScrollView(
  slivers: [
    SliverAppBar(
      expandedHeight: 200,
      floating: false,
      pinned: true,
      flexibleSpace: FlexibleSpaceBar(title: Text('Scroll')),
    ),
    SliverList(delegate: SliverChildBuilderDelegate(
      (context, index) => ListTile(title: Text('Item $index')),
      childCount: 100,
    )),
  ],
)

Animations basics

// Implicit animations — animate property changes
AnimatedContainer(
  duration: Duration(milliseconds: 300),
  curve: Curves.easeInOut,
  width: isExpanded ? 200 : 100,
  height: isExpanded ? 200 : 100,
  color: isExpanded ? Colors.blue : Colors.grey,
)

AnimatedOpacity(opacity: isVisible ? 1.0 : 0.0, duration: Duration(milliseconds: 300))
AnimatedPositioned(duration: Duration(milliseconds: 300), left: offset, top: offset, child: icon)
AnimatedSwitcher(duration: Duration(milliseconds: 300), child: isVisible ? Text('Yes') : Text('No'))