Flutter Layout Tips: Mastering Stack and Positioned for Creative UIs
Introduction
Flutter's rich set of layout widgets provides immense flexibility for building beautiful and functional user interfaces. Among the most powerful tools for creating layered, overlapping, or precisely placed elements are the Stack and Positioned widgets. This article delves into how these two widgets work in tandem to unlock a new level of creativity in your Flutter UIs.
Understanding the Stack Widget
The Stack widget in Flutter is designed to layer multiple widgets on top of one another, much like a stack of papers. The first child in the Stack's children list is drawn at the bottom, and subsequent children are drawn on top of the previous ones. By default, all children of a Stack are aligned to the top-left corner.
You can control the default alignment of non-Positioned children using the alignment property of the Stack.
Basic Stack Example
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Basic Stack Example')),
body: Center(
child: Stack(
alignment: Alignment.center, // Aligns children to the center
children: [
Container(
width: 200,
height: 200,
color: Colors.blue,
),
Container(
width: 150,
height: 150,
color: Colors.red,
),
Container(
width: 100,
height: 100,
color: Colors.green,
),
],
),
),
),
);
}
}
In this example, the blue container is at the bottom, followed by red, and then green, all centered within the Stack due to the alignment property.
Leveraging the Positioned Widget
While Stack allows layering, Positioned is the key to precisely placing widgets within that Stack. A Positioned widget must be a direct child of a Stack (or an ancestor that resolves to a Stack, but typically direct). It takes properties like top, bottom, left, right, width, and height to specify its exact location and size relative to the Stack's boundaries.
When a child of a Stack is wrapped in a Positioned widget, its position is no longer affected by the Stack's alignment property. Instead, it relies entirely on the Positioned widget's properties.
Positioned Example
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Stack and Positioned Example')),
body: Center(
child: Container(
width: 300,
height: 300,
color: Colors.grey[200],
child: Stack(
children: [
// Background element
Positioned.fill(
child: Image.network(
'https://via.placeholder.com/300x300/CCCCCC/FFFFFF?text=Background',
fit: BoxFit.cover,
),
),
// Top-left text
Positioned(
top: 20,
left: 20,
child: Text(
'Top Left',
style: TextStyle(color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold),
),
),
// Bottom-right button
Positioned(
bottom: 20,
right: 20,
child: ElevatedButton(
onPressed: () {},
child: Text('Action'),
),
),
// Centered icon with custom size
Positioned(
top: 100,
left: 100,
width: 100,
height: 100,
child: Icon(
Icons.star,
color: Colors.yellow,
size: 80,
),
),
],
),
),
),
),
);
}
}
Notice Positioned.fill which is a convenient constructor to make a child fill the entire Stack, often used for background images or overlays.
Creative UI Use Cases and Tips
The combination of Stack and Positioned opens up a world of possibilities for intricate and visually appealing UIs:
1. Overlapping Profile Pictures and Badges
A common pattern is a user's profile picture with a small status indicator or badge overlapping it.
Stack(
children: [
CircleAvatar(
radius: 40,
backgroundImage: NetworkImage('https://via.placeholder.com/150'),
),
Positioned(
bottom: 0,
right: 0,
child: Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: Colors.green,
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 2),
),
),
),
],
)
2. Text Over Images
Placing text or other widgets directly on top of an image for captions, titles, or interactive elements.
SizedBox(
width: double.infinity,
height: 200,
child: Stack(
children: [
Image.network(
'https://via.placeholder.com/600x200/FF0000/FFFFFF?text=Beautiful+Landscape',
fit: BoxFit.cover,
width: double.infinity,
),
Positioned(
bottom: 10,
left: 10,
child: Text(
'Sunset View',
style: TextStyle(
color: Colors.white,
fontSize: 28,
fontWeight: FontWeight.bold,
shadows: [
Shadow(
blurRadius: 5.0,
color: Colors.black.withOpacity(0.5),
offset: Offset(2.0, 2.0),
),
],
),
),
),
],
),
)
3. Custom Overlays and Modals
For custom loading indicators, tooltips, or pop-up messages that need to float above the primary content.
Stack(
children: [
// Main content of your screen
ListView(
children: List.generate(20, (index) => ListTile(title: Text('Item $index'))),
),
// Overlay (e.g., loading spinner)
Positioned.fill(
child: Container(
color: Colors.black.withOpacity(0.5),
child: Center(
child: CircularProgressIndicator(),
),
),
),
],
)
4. Floating Action Buttons with Custom Placement
While Scaffold provides a floatingActionButton, Stack and Positioned can be used for more complex custom floating elements, or multiple floating elements.
Stack(
children: [
// Your main content
Container(color: Colors.white),
Positioned(
bottom: 20,
right: 20,
child: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
),
),
Positioned(
bottom: 90, // Position another button above the first
right: 20,
child: FloatingActionButton(
mini: true,
onPressed: () {},
child: Icon(Icons.share),
),
),
],
)
Advanced Tips and Considerations
Combine with Align Widget
For children that don't need pixel-perfect positioning but rather alignment to an edge or center, using an Align widget as a child of Stack can be simpler than Positioned. Align respects the Stack's size and places its child according to its alignment property.
Stack(
children: [
Container(width: 200, height: 200, color: Colors.blueGrey),
Align(
alignment: Alignment.bottomLeft,
child: Text('Bottom Left', style: TextStyle(color: Colors.white)),
),
Align(
alignment: Alignment.topRight,
child: Icon(Icons.info, color: Colors.yellow),
),
],
)
Responsiveness with MediaQuery
When using Positioned, especially with fixed pixel values, be mindful of different screen sizes. For responsive designs, consider using percentages of screen width/height obtained via MediaQuery.of(context).size.
// Inside a Widget's build method
double screenWidth = MediaQuery.of(context).size.width;
Stack(
children: [
// ...
Positioned(
left: screenWidth * 0.1, // 10% from left
top: screenWidth * 0.05, // 5% from top (relative to width for aspect ratio)
child: Container(
width: screenWidth * 0.3,
height: 50,
color: Colors.purple,
),
),
],
)
Performance
While Stack is generally efficient, avoid overly deep or complex widget trees within its children, especially if many are being constantly rebuilt. For static overlays or a few dynamic elements, performance impact is usually negligible.
Conclusion
The Stack and Positioned widgets are indispensable tools in a Flutter developer's arsenal for crafting sophisticated and visually appealing UIs. By understanding how to layer widgets with Stack and precisely control their placement with Positioned, you can move beyond conventional layouts and bring your most creative design ideas to life. Experiment with these widgets to discover the endless possibilities they offer for unique and engaging user experiences.