staggered_flow_grid 1.0.1
staggered_flow_grid: ^1.0.1 copied to clipboard
A high-performance, zero-dependency Flutter widget that renders children in a staggered waterfall grid layout using a custom shortest-column RenderObject.
// example/lib/main.dart
//
// StaggeredFlowGrid — production-quality example app.
//
// Tabs:
// 1. Gallery — Pinterest-style image grid (CachedNetworkImage + shimmer)
// 2. Cards — Mixed-height content cards (articles, stats, quotes)
// 3. Playground — Live controls + real-time grid preview
import 'package:flutter/material.dart';
import 'screens/cards_screen.dart';
import 'screens/gallery_screen.dart';
import 'screens/playground_screen.dart';
import 'theme/app_theme.dart';
void main() => runApp(const ExampleApp());
// ---------------------------------------------------------------------------
// Root app — owns ThemeMode state
// ---------------------------------------------------------------------------
class ExampleApp extends StatefulWidget {
const ExampleApp({super.key});
@override
State<ExampleApp> createState() => _ExampleAppState();
}
class _ExampleAppState extends State<ExampleApp> {
ThemeMode _themeMode = ThemeMode.system;
void _toggleTheme() {
setState(() {
_themeMode =
_themeMode == ThemeMode.dark ? ThemeMode.light : ThemeMode.dark;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'StaggeredFlowGrid',
debugShowCheckedModeBanner: false,
theme: AppTheme.light(),
darkTheme: AppTheme.dark(),
themeMode: _themeMode,
home: _HomePage(
themeMode: _themeMode,
onToggleTheme: _toggleTheme,
),
);
}
}
// ---------------------------------------------------------------------------
// Home page — owns grid configuration state
// ---------------------------------------------------------------------------
class _HomePage extends StatefulWidget {
const _HomePage({
required this.themeMode,
required this.onToggleTheme,
});
final ThemeMode themeMode;
final VoidCallback onToggleTheme;
@override
State<_HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<_HomePage> {
int _currentIndex = 0;
// Grid configuration shared across all tabs.
int _columns = 2;
double _mainSpacing = 8.0;
double _crossSpacing = 8.0;
static const List<(String label, IconData icon, IconData activeIcon)>
_destinations = [
('Gallery', Icons.photo_library_outlined, Icons.photo_library_rounded),
('Cards', Icons.dashboard_outlined, Icons.dashboard_rounded),
('Playground', Icons.tune_outlined, Icons.tune_rounded),
];
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
// Build screens lazily via IndexedStack so scroll state is preserved.
final screens = [
GalleryScreen(
columns: _columns,
mainSpacing: _mainSpacing,
crossSpacing: _crossSpacing,
),
CardsScreen(
columns: _columns,
mainSpacing: _mainSpacing,
crossSpacing: _crossSpacing,
),
PlaygroundScreen(
columns: _columns,
mainSpacing: _mainSpacing,
crossSpacing: _crossSpacing,
onColumnsChanged: (v) => setState(() => _columns = v),
onMainSpacingChanged: (v) => setState(() => _mainSpacing = v),
onCrossSpacingChanged: (v) => setState(() => _crossSpacing = v),
),
];
return Scaffold(
appBar: AppBar(
title: const Text('StaggeredFlowGrid'),
actions: [
// Column count badge — shows current setting from any tab
if (_currentIndex != 2)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: _ColumnBadge(columns: _columns),
),
IconButton(
icon: Icon(
isDark ? Icons.light_mode_rounded : Icons.dark_mode_rounded,
),
tooltip: 'Toggle theme',
onPressed: widget.onToggleTheme,
),
],
),
body: IndexedStack(
index: _currentIndex,
children: screens,
),
bottomNavigationBar: NavigationBar(
selectedIndex: _currentIndex,
onDestinationSelected: (i) => setState(() => _currentIndex = i),
destinations: _destinations
.map(
(d) => NavigationDestination(
icon: Icon(d.$2),
selectedIcon: Icon(d.$3),
label: d.$1,
),
)
.toList(),
),
);
}
}
// ---------------------------------------------------------------------------
// Small column-count indicator in the AppBar
// ---------------------------------------------------------------------------
class _ColumnBadge extends StatelessWidget {
const _ColumnBadge({required this.columns});
final int columns;
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Center(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: cs.primaryContainer,
borderRadius: BorderRadius.circular(20),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.view_column_rounded, size: 14, color: cs.primary),
const SizedBox(width: 4),
Text(
'$columns',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w700,
color: cs.onPrimaryContainer,
),
),
],
),
),
);
}
}