
Flutter Ui Ux
- 775 installs
- 4 repo stars
- Updated December 6, 2025
- ajianaz/skills-collection
flutter-ui-ux is a Claude skill that helps developers generate high-quality responsive Flutter UI components, animations, themes, and screens using modern mobile-first patterns.
About
flutter-ui-ux is a comprehensive Flutter UI and UX skill from ajianaz/skills-collection for building mobile interfaces with modern design patterns. The skill supports creating Flutter UI components and screens, implementing animations and transitions, designing responsive layouts, building custom widgets and themes, and optimizing UI performance and accessibility. Developers reach for flutter-ui-ux when prompts mention create Flutter UI, build Flutter screen, Flutter animations, responsive Flutter layout, custom Flutter widgets, or Flutter theme design. It functions as a pattern library and implementation guide for beautiful, animated, mobile-first Flutter applications rather than backend or state-management-only work.
- 5-phase sequential workflow that analyzes requirements before generating code
- Focuses on widget composition, responsive design, purposeful animations, custom themes and 60fps performance
- Creates reusable, accessible, and maintainable Flutter widgets and screens
- Implements smooth transitions, micro-interactions and platform-specific considerations for iOS and Android
- Optimizes UI for accessibility, branding consistency and resource efficiency
Flutter Ui Ux by the numbers
- 775 all-time installs (skills.sh)
- Ranked #482 of 1,880 Design & UI/UX skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
npx skills add https://github.com/ajianaz/skills-collection --skill flutter-ui-uxAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 775 |
|---|---|
| repo stars | ★ 4 |
| Last updated | December 6, 2025 |
| Repository | ajianaz/skills-collection ↗ |
How do you build animated Flutter UI screens?
Generate high-quality, responsive Flutter UI components, animations, themes and screens using modern mobile-first patterns.
Who is it for?
Mobile developers implementing polished Flutter UI, motion, and theming who want agent-guided modern layout patterns.
Skip if: Backend API design, non-Flutter mobile stacks, or projects needing only Dart business logic without UI work.
When should I use this skill?
The user asks to create Flutter UI, build screens, add animations, design responsive layouts, or customize Flutter themes and widgets.
What you get
Flutter widgets, themed screens, responsive layouts, and animation implementations ready to drop into a project.
- Flutter screen code
- Custom widgets
- Theme and animation implementations
Files
Flutter UI/UX Development
Create beautiful, responsive, and animated Flutter applications with modern design patterns and best practices.
Core Philosophy
"Mobile-first, animation-enhanced, accessible design" - Focus on:
| Priority | Area | Purpose |
|---|---|---|
| 1 | Widget Composition | Reusable, maintainable UI components |
| 2 | Responsive Design | Adaptive layouts for all screen sizes |
| 3 | Animations | Smooth, purposeful transitions and micro-interactions |
| 4 | Custom Themes | Consistent, branded visual identity |
| 5 | Performance | 60fps rendering and optimal resource usage |
Development Workflow
Execute phases sequentially. Complete each before proceeding.
Phase 1: Analyze Requirements
1. Understand app structure - Identify existing widgets, screens, and navigation 2. Design system review - Check existing themes, colors, and typography 3. Platform considerations - Note iOS/Android specific requirements 4. Performance constraints - Identify animation complexity and rendering needs
Output: UI requirements analysis with component breakdown.
Phase 2: Design Widget Architecture
1. Widget hierarchy planning - Design composition tree 2. State management strategy - Choose StatefulWidget vs StatelessWidget 3. Custom widget identification - Plan reusable components 4. Theme integration - Define color schemes and typography
Output: Widget architecture diagram and component specifications.
Phase 3: Implement Core UI
1. Create base widgets - Build foundational components 2. Implement responsive layouts - Use MediaQuery, LayoutBuilder, Flex/Expanded 3. Add custom themes - ThemeData, ColorScheme, TextThemes 4. Integrate navigation - Implement routing and transitions
Widget Composition Patterns:
// ✅ DO: Compose small, reusable widgets
class CustomCard extends StatelessWidget {
final Widget child;
final EdgeInsets padding;
const CustomCard({required this.child, this.padding = EdgeInsets.all(16)});
@override
Widget build(BuildContext context) {
return Card(
elevation: 4,
child: Padding(padding: padding, child: child),
);
}
}
// ✅ DO: Use const constructors where possible
const Icon(Icons.add) // Better than Icon(Icons.add)Phase 4: Add Animations
1. Implicit animations - AnimatedContainer, AnimatedOpacity 2. Explicit animations - AnimationController with Tween 3. Hero animations - Screen transitions with Hero widgets 4. Micro-interactions - Button presses, hover effects, loading states
Animation Performance Rules:
// ✅ DO: Use performance-optimized animations
AnimatedBuilder(
animation: controller,
builder: (context, child) => Transform.rotate(
angle: controller.value * 2 * math.pi,
child: child, // Pass child to avoid rebuilding
),
child: const Icon(Icons.refresh),
)
// ❌ DON'T: Animate expensive operations
// Avoid animating complex layouts or heavy widgetsPhase 5: Optimize and Test
1. Performance profiling - Use Flutter DevTools 2. Accessibility testing - Screen readers, contrast ratios 3. Responsive testing - Multiple screen sizes and orientations 4. Animation smoothness - 60fps validation
Quick Reference
Responsive Design Patterns
| Technique | Use Case | Implementation |
|---|---|---|
| LayoutBuilder | Responsive layouts | LayoutBuilder(builder: (context, constraints) => ...) |
| MediaQuery | Screen info | MediaQuery.of(context).size.width |
| Flexible/Expanded | Flex layouts | Flexible(child: ...) or Expanded(child: ...) |
| AspectRatio | Fixed ratios | AspectRatio(aspectRatio: 16/9, child: ...) |
Animation Types
| Type | Widget | Duration | Use Case |
|---|---|---|---|
| Fade | AnimatedOpacity | 200-300ms | Show/hide content |
| Slide | SlideTransition | 250-350ms | Screen transitions |
| Scale | AnimatedScale | 150-250ms | Button presses |
| Rotation | RotationTransition | 1000-2000ms | Loading indicators |
Custom Widget Examples
Themed Button:
class ThemedButton extends StatelessWidget {
final String text;
final VoidCallback onPressed;
const ThemedButton({required this.text, required this.onPressed});
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
foregroundColor: Theme.of(context).colorScheme.onPrimary,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
child: Text(text),
);
}
}Responsive Card:
class ResponsiveCard extends StatelessWidget {
final Widget child;
const ResponsiveCard({required this.child});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth > 600) {
return _buildWideLayout(child);
} else {
return _buildNarrowLayout(child);
}
},
);
}
Widget _buildWideLayout(Widget child) {
return Card(
margin: const EdgeInsets.all(16),
child: Padding(padding: const EdgeInsets.all(24), child: child),
);
}
Widget _buildNarrowLayout(Widget child) {
return Card(
margin: const EdgeInsets.all(8),
child: Padding(padding: const EdgeInsets.all(16), child: child),
);
}
}Resources
- Widget patterns: See
references/widget-patterns.md - Animation examples: See
references/animation-patterns.md - Theme templates: See
references/theme-templates.md - Performance guide: See
references/performance-optimization.md
Technical Stack
- Core Widgets: StatelessWidget, StatefulWidget, InheritedWidget
- Layout: Row, Column, Stack, GridView, ListView
- Animation: AnimationController, Tween, AnimatedWidget
- Themes: ThemeData, ColorScheme, TextTheme
- Navigation: Navigator, MaterialPageRoute, Hero
Accessibility (Required)
Always implement:
// Semantic labels for screen readers
Semantics(
label: 'Add item to cart',
button: true,
child: IconButton(icon: Icon(Icons.add_cart), onPressed: () {}),
)
// High contrast support
Theme.of(context).colorScheme.contrast() == Brightness.dark
// Font scaling
MediaQuery.of(context).accessibleNavigationPerformance Guidelines
- Use
constwidgets where possible - Prefer
ListView.builderfor long lists - Avoid unnecessary rebuilds with
constkeys - Use
RepaintBoundaryfor complex animations - Profile with Flutter DevTools regularly
---
This Flutter UI/UX skill transforms mobile app development into a systematic process that ensures beautiful, responsive, and performant applications with exceptional user experiences.
Flutter App Template
Basic App Structure
Complete starter template for Flutter applications.
// main.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'app/theme/app_theme.dart';
import 'app/screens/home_screen.dart';
import 'app/screens/detail_screen.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter App',
debugShowCheckedModeBanner: false,
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
themeMode: ThemeMode.system,
home: HomeScreen(),
routes: {
'/detail': (context) => DetailScreen(),
},
);
}
}Responsive App Template
Mobile-first responsive design.
// main.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'app/theme/responsive_theme.dart';
import 'app/screens/home_screen.dart';
import 'app/widgets/responsive_layout.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Responsive Flutter App',
debugShowCheckedModeBanner: false,
theme: ResponsiveTheme.lightTheme,
darkTheme: ResponsiveTheme.darkTheme,
themeMode: ThemeMode.system,
home: ResponsiveLayout(
mobile: HomeScreen(),
tablet: HomeScreen(),
desktop: HomeScreen(),
),
);
}
}
// app/widgets/responsive_layout.dart
import 'package:flutter/material.dart';
class ResponsiveLayout extends StatelessWidget {
final Widget mobile;
final Widget? tablet;
final Widget? desktop;
const ResponsiveLayout({
required this.mobile,
this.tablet,
this.desktop,
});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth >= 1200 && desktop != null) {
return desktop!;
} else if (constraints.maxWidth >= 800 && tablet != null) {
return tablet!;
} else {
return mobile;
}
},
);
}
}Navigation Template
Structured navigation with routes.
// app/routes/app_routes.dart
class AppRoutes {
static const String home = '/';
static const String detail = '/detail';
static const String settings = '/settings';
static const String profile = '/profile';
}
class RouteGenerator {
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case AppRoutes.home:
return MaterialPageRoute(
builder: (_) => HomeScreen(),
settings: settings,
);
case AppRoutes.detail:
final String id = settings.arguments as String;
return MaterialPageRoute(
builder: (_) => DetailScreen(id: id),
settings: settings,
);
case AppRoutes.settings:
return MaterialPageRoute(
builder: (_) => SettingsScreen(),
settings: settings,
);
default:
return MaterialPageRoute(
builder: (_) => NotFoundScreen(),
);
}
}
}
// app/navigation/navigation_service.dart
import 'package:flutter/material.dart';
class NavigationService {
static final GlobalKey<NavigatorState> navigatorKey =
GlobalKey<NavigatorState>();
static Future<dynamic> navigateTo(String routeName, {Object? arguments}) {
return navigatorKey.currentState!.pushNamed(routeName, arguments: arguments);
}
static void goBack() {
return navigatorKey.currentState!.pop();
}
static Future<dynamic> replaceWith(String routeName, {Object? arguments}) {
return navigatorKey.currentState!.pushReplacementNamed(
routeName,
arguments: arguments,
);
}
static Future<dynamic> navigateToAndClearStack(String routeName, {Object? arguments}) {
return navigatorKey.currentState!.pushNamedAndRemoveUntil(
routeName,
(route) => false,
arguments: arguments,
);
}
}State Management Template
Provider-based state management.
// app/providers/app_state_provider.dart
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class AppState extends ChangeNotifier {
bool _isLoading = false;
String? _errorMessage;
bool get isLoading => _isLoading;
String? get errorMessage => _errorMessage;
void setLoading(bool loading) {
_isLoading = loading;
notifyListeners();
}
void setError(String? error) {
_errorMessage = error;
notifyListeners();
}
void clearError() {
_errorMessage = null;
notifyListeners();
}
}
// app/providers/user_provider.dart
import 'package:flutter/foundation.dart';
class UserProvider extends ChangeNotifier {
User? _user;
bool _isLoggedIn = false;
User? get user => _user;
bool get isLoggedIn => _isLoggedIn;
Future<void> login(String email, String password) async {
try {
// Login logic here
_user = User(id: '1', email: email);
_isLoggedIn = true;
notifyListeners();
} catch (e) {
// Handle error
}
}
void logout() {
_user = null;
_isLoggedIn = false;
notifyListeners();
}
}
class User {
final String id;
final String email;
User({required this.id, required this.email});
}
// app/widgets/stateful_builder.dart
import 'package:flutter/material.dart';
class StatefulBuilder<T> extends StatefulWidget {
final T initialValue;
final Widget Function(
T value,
void Function(T) updateValue,
) builder;
const StatefulBuilder({
required this.initialValue,
required this.builder,
});
@override
_StatefulBuilderState<T> createState() => _StatefulBuilderState<T>();
}
class _StatefulBuilderState<T> extends State<StatefulBuilder<T>> {
late T _value;
@override
void initState() {
super.initState();
_value = widget.initialValue;
}
void _updateValue(T newValue) {
setState(() {
_value = newValue;
});
}
@override
Widget build(BuildContext context) {
return widget.builder(_value, _updateValue);
}
}API Service Template
Structured API communication.
// app/services/api_service.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
class ApiService {
static const String baseUrl = 'https://api.example.com';
static const Duration timeout = Duration(seconds: 30);
static Map<String, String> get headers => {
'Content-Type': 'application/json',
'Accept': 'application/json',
};
static Future<Map<String, dynamic>> get(String endpoint) async {
try {
final response = await http.get(
Uri.parse('$baseUrl$endpoint'),
headers: headers,
).timeout(timeout);
return _handleResponse(response);
} catch (e) {
throw _handleError(e);
}
}
static Future<Map<String, dynamic>> post(
String endpoint,
Map<String, dynamic> data,
) async {
try {
final response = await http.post(
Uri.parse('$baseUrl$endpoint'),
headers: headers,
body: json.encode(data),
).timeout(timeout);
return _handleResponse(response);
} catch (e) {
throw _handleError(e);
}
}
static Future<Map<String, dynamic>> put(
String endpoint,
Map<String, dynamic> data,
) async {
try {
final response = await http.put(
Uri.parse('$baseUrl$endpoint'),
headers: headers,
body: json.encode(data),
).timeout(timeout);
return _handleResponse(response);
} catch (e) {
throw _handleError(e);
}
}
static Future<void> delete(String endpoint) async {
try {
final response = await http.delete(
Uri.parse('$baseUrl$endpoint'),
headers: headers,
).timeout(timeout);
_handleResponse(response);
} catch (e) {
throw _handleError(e);
}
}
static Map<String, dynamic> _handleResponse(http.Response response) {
if (response.statusCode >= 200 && response.statusCode < 300) {
return json.decode(response.body);
} else {
throw ApiException(
'API Error: ${response.statusCode}',
response.statusCode,
);
}
}
static Exception _handleError(dynamic error) {
if (error is http.TimeoutException) {
return ApiException('Request timeout', 408);
} else if (error is http.ClientException) {
return ApiException('Network error', 0);
} else {
return ApiException('Unknown error', 0);
}
}
}
class ApiException implements Exception {
final String message;
final int statusCode;
ApiException(this.message, this.statusCode);
@override
String toString() => 'ApiException: $message ($statusCode)';
}Widget Templates
Common reusable widgets.
// app/widgets/custom_button.dart
import 'package:flutter/material.dart';
class CustomButton extends StatelessWidget {
final String text;
final VoidCallback onPressed;
final bool isLoading;
final Color? color;
final Color? textColor;
final double? width;
final double? height;
const CustomButton({
required this.text,
required this.onPressed,
this.isLoading = false,
this.color,
this.textColor,
this.width,
this.height,
});
@override
Widget build(BuildContext context) {
return SizedBox(
width: width,
height: height ?? 48,
child: ElevatedButton(
onPressed: isLoading ? null : onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: color ?? Theme.of(context).primaryColor,
foregroundColor: textColor ?? Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: isLoading
? SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
textColor ?? Colors.white,
),
),
)
: Text(text),
),
);
}
}
// app/widgets/custom_text_field.dart
import 'package:flutter/material.dart';
class CustomTextField extends StatelessWidget {
final String? label;
final String? hint;
final String? errorText;
final TextEditingController? controller;
final bool obscureText;
final TextInputType keyboardType;
final Function(String)? onChanged;
final Function(String)? onSubmitted;
final Widget? prefixIcon;
final Widget? suffixIcon;
const CustomTextField({
this.label,
this.hint,
this.errorText,
this.controller,
this.obscureText = false,
this.keyboardType = TextInputType.text,
this.onChanged,
this.onSubmitted,
this.prefixIcon,
this.suffixIcon,
});
@override
Widget build(BuildContext context) {
return TextField(
controller: controller,
obscureText: obscureText,
keyboardType: keyboardType,
onChanged: onChanged,
onSubmitted: onSubmitted,
decoration: InputDecoration(
labelText: label,
hintText: hint,
errorText: errorText,
prefixIcon: prefixIcon,
suffixIcon: suffixIcon,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: Theme.of(context).primaryColor,
width: 2,
),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: Theme.of(context).errorColor,
width: 2,
),
),
contentPadding: EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
),
);
}
}
// app/widgets/loading_widget.dart
import 'package:flutter/material.dart';
class LoadingWidget extends StatelessWidget {
final String? message;
final double? size;
const LoadingWidget({
this.message,
this.size = 24,
});
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: size,
height: size,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
Theme.of(context).primaryColor,
),
),
),
if (message != null) ...[
SizedBox(height: 16),
Text(
message!,
style: Theme.of(context).textTheme.bodyMedium,
),
],
],
),
);
}
}
// app/widgets/empty_state_widget.dart
import 'package:flutter/material.dart';
class EmptyStateWidget extends StatelessWidget {
final String title;
final String? subtitle;
final Widget? action;
final IconData? icon;
const EmptyStateWidget({
required this.title,
this.subtitle,
this.action,
this.icon,
});
@override
Widget build(BuildContext context) {
return Center(
child: Padding(
padding: EdgeInsets.all(32),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (icon != null) ...[
Icon(
icon!,
size: 64,
color: Theme.of(context).disabledColor,
),
SizedBox(height: 16),
],
Text(
title,
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
color: Theme.of(context).disabledColor,
),
textAlign: TextAlign.center,
),
if (subtitle != null) ...[
SizedBox(height: 8),
Text(
subtitle!,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).disabledColor,
),
textAlign: TextAlign.center,
),
],
if (action != null) ...[
SizedBox(height: 24),
action!,
],
],
),
),
);
}
}Screen Templates
Common screen patterns.
// app/screens/base_screen.dart
import 'package:flutter/material.dart';
class BaseScreen extends StatelessWidget {
final Widget body;
final String? title;
final List<Widget>? actions;
final Widget? floatingActionButton;
final Widget? bottomNavigationBar;
final bool showAppBar;
const BaseScreen({
required this.body,
this.title,
this.actions,
this.floatingActionButton,
this.bottomNavigationBar,
this.showAppBar = true,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: showAppBar
? AppBar(
title: title != null ? Text(title!) : null,
actions: actions,
)
: null,
body: SafeArea(child: body),
floatingActionButton: floatingActionButton,
bottomNavigationBar: bottomNavigationBar,
);
}
}
// app/screens/list_screen.dart
import 'package:flutter/material.dart';
class ListScreen<T> extends StatelessWidget {
final List<T> items;
final Widget Function(BuildContext, T) itemBuilder;
final String? title;
final Function(T)? onItemTap;
final Widget? emptyState;
final bool isLoading;
const ListScreen({
required this.items,
required this.itemBuilder,
this.title,
this.onItemTap,
this.emptyState,
this.isLoading = false,
});
@override
Widget build(BuildContext context) {
if (isLoading) {
return LoadingWidget();
}
if (items.isEmpty) {
return emptyState ??
EmptyStateWidget(
title: 'No items found',
icon: Icons.inbox_outlined,
);
}
return BaseScreen(
title: title,
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return GestureDetector(
onTap: () => onItemTap?.call(item),
child: itemBuilder(context, item),
);
},
),
);
}
}Usage Example
Complete app using all templates.
// main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'app/providers/user_provider.dart';
import 'app/services/api_service.dart';
import 'app/widgets/custom_button.dart';
import 'app/screens/base_screen.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => UserProvider(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Template App',
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
themeMode: ThemeMode.system,
home: HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer<UserProvider>(
builder: (context, userProvider, child) {
return BaseScreen(
title: 'Home',
body: Column(
children: [
if (userProvider.isLoggedIn) ...[
Text('Welcome ${userProvider.user?.email}'),
CustomButton(
text: 'Logout',
onPressed: () => userProvider.logout(),
),
] else ...[
Text('Please login'),
CustomButton(
text: 'Login',
onPressed: () => _showLoginDialog(context),
),
],
],
),
);
},
);
}
void _showLoginDialog(BuildContext context) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Login'),
content: CustomTextField(
label: 'Email',
hint: 'Enter your email',
),
actions: [
CustomButton(
text: 'Cancel',
onPressed: () => Navigator.pop(context),
),
CustomButton(
text: 'Login',
onPressed: () {
// Login logic
Navigator.pop(context);
},
),
],
),
);
}
}Flutter Animation Patterns
Implicit Animations
Simple animations with minimal code.
1. AnimatedContainer
Smooth property transitions.
class AnimatedCard extends StatefulWidget {
final bool isSelected;
final Widget child;
const AnimatedCard({required this.isSelected, required this.child});
@override
_AnimatedCardState createState() => _AnimatedCardState();
}
class _AnimatedCardState extends State<AnimatedCard> {
bool _isHovered = false;
@override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (_) => setState(() => _isHovered = true),
onExit: (_) => setState(() => _isHovered = false),
child: AnimatedContainer(
duration: Duration(milliseconds: 200),
curve: Curves.easeInOut,
width: _isHovered ? 200 : 180,
height: _isHovered ? 120 : 100,
decoration: BoxDecoration(
color: widget.isSelected
? Theme.of(context).primaryColor
: (_isHovered ? Colors.grey[200] : Colors.grey[100]),
borderRadius: BorderRadius.circular(_isHovered ? 16 : 8),
boxShadow: _isHovered
? [BoxShadow(color: Colors.black26, blurRadius: 8, offset: Offset(0, 4))]
: [BoxShadow(color: Colors.black12, blurRadius: 2, offset: Offset(0, 1))],
),
child: widget.child,
),
);
}
}2. AnimatedOpacity & AnimatedScale
Fade and scale effects.
class FadeInButton extends StatefulWidget {
final String text;
final VoidCallback onPressed;
final Duration delay;
const FadeInButton({
required this.text,
required this.onPressed,
this.delay = Duration.zero,
});
@override
_FadeInButtonState createState() => _FadeInButtonState();
}
class _FadeInButtonState extends State<FadeInButton> {
bool _isVisible = false;
@override
void initState() {
super.initState();
Future.delayed(widget.delay, () {
if (mounted) setState(() => _isVisible = true);
});
}
@override
Widget build(BuildContext context) {
return AnimatedOpacity(
opacity: _isVisible ? 1.0 : 0.0,
duration: Duration(milliseconds: 600),
curve: Curves.easeOut,
child: AnimatedScale(
scale: _isVisible ? 1.0 : 0.8,
duration: Duration(milliseconds: 600),
curve: Curves.elasticOut,
child: ElevatedButton(
onPressed: widget.onPressed,
child: Text(widget.text),
),
),
);
}
}Explicit Animations
Fine-grained control with AnimationController.
1. Custom Tween Animation
Controlled property animation.
class AnimatedProgress extends StatefulWidget {
final double progress; // 0.0 to 1.0
final Color color;
final Duration duration;
const AnimatedProgress({
required this.progress,
this.color = Colors.blue,
this.duration = const Duration(milliseconds: 800),
});
@override
_AnimatedProgressState createState() => _AnimatedProgressState();
}
class _AnimatedProgressState extends State<AnimatedProgress>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: widget.duration,
vsync: this,
);
_animation = Tween<double>(
begin: 0.0,
end: widget.progress,
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.easeInOutCubic,
));
_controller.forward();
}
@override
void didUpdateWidget(AnimatedProgress oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.progress != widget.progress) {
_animation = Tween<double>(
begin: _controller.value,
end: widget.progress,
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.easeInOutCubic,
));
_controller.forward(from: _controller.value);
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return CustomPaint(
painter: ProgressPainter(_animation.value, widget.color),
child: child,
);
},
child: Container(),
);
}
}
class ProgressPainter extends CustomPainter {
final double progress;
final Color color;
ProgressPainter(this.progress, this.color);
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = color.withOpacity(0.3)
..style = PaintingStyle.fill;
// Background
canvas.drawRRect(
RRect.fromRectAndRadius(
Rect.fromLTWH(0, 0, size.width, size.height),
Radius.circular(size.height / 2),
),
paint,
);
// Progress
paint.color = color;
canvas.drawRRect(
RRect.fromRectAndRadius(
Rect.fromLTWH(0, 0, size.width * progress, size.height),
Radius.circular(size.height / 2),
),
paint,
);
}
@override
bool shouldRepaint(ProgressPainter oldDelegate) {
return oldDelegate.progress != progress || oldDelegate.color != color;
}
}2. Staggered Animation
Sequential animations with delays.
class StaggeredAnimationDemo extends StatefulWidget {
final List<Widget> children;
const StaggeredAnimationDemo({required this.children});
@override
_StaggeredAnimationDemoState createState() => _StaggeredAnimationDemoState();
}
class _StaggeredAnimationDemoState extends State<AnimatedListDemo>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late List<Animation<double>> _animations;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(milliseconds: 1200),
vsync: this,
);
_animations = List.generate(
widget.children.length,
(index) => Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _controller,
curve: Interval(
index * 0.1, // Start delay
0.5 + (index * 0.1), // End delay
curve: Curves.easeOutCubic,
),
)),
);
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: List.generate(
widget.children.length,
(index) => AnimatedBuilder(
animation: _animations[index],
builder: (context, child) {
return Transform.translate(
offset: Offset(0, 50 * (1 - _animations[index].value)),
child: Opacity(
opacity: _animations[index].value,
child: child,
),
);
},
child: widget.children[index],
),
),
);
}
}Physics Animations
Realistic motion with physics simulation.
1. Spring Animation
Bouncy, springy effects.
class SpringButton extends StatefulWidget {
final Widget child;
final VoidCallback onPressed;
const SpringButton({required this.child, required this.onPressed});
@override
_SpringButtonState createState() => _SpringButtonState();
}
class _SpringButtonState extends State<SpringButton>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scaleAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(milliseconds: 500),
vsync: this,
);
_scaleAnimation = Tween<double>(
begin: 1.0,
end: 0.95,
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.elasticIn,
));
}
void _handleTap() {
_controller.forward().then((_) {
_controller.reverse();
});
widget.onPressed();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: _handleTap,
child: AnimatedBuilder(
animation: _scaleAnimation,
builder: (context, child) {
return Transform.scale(
scale: _scaleAnimation.value,
child: child,
);
},
child: widget.child,
),
);
}
}2. Draggable Card
Physics-based dragging.
class DraggableCard extends StatefulWidget {
final Widget child;
final VoidCallback? onSwipeComplete;
const DraggableCard({required this.child, this.onSwipeComplete});
@override
_DraggableCardState createState() => _DraggableCardState();
}
class _DraggableCardState extends State<DraggableCard>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<Offset> _slideAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(milliseconds: 300),
vsync: this,
);
_slideAnimation = Tween<Offset>(
begin: Offset.zero,
end: Offset(1.5, 0), // Swipe off screen
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.easeOutCubic,
));
}
void _handlePanUpdate(DragUpdateDetails details) {
// Handle drag update
}
void _handlePanEnd(DragEndDetails details) {
if (details.velocity.pixelsPerSecond.dx > 300) {
// Swipe right
_controller.forward().then((_) {
widget.onSwipeComplete?.call();
});
} else {
// Snap back
_controller.reverse();
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onPanUpdate: _handlePanUpdate,
onPanEnd: _handlePanEnd,
child: AnimatedBuilder(
animation: _slideAnimation,
builder: (context, child) {
return SlideTransition(
position: _slideAnimation,
child: child,
);
},
child: widget.child,
),
);
}
}Hero Animations
Screen transitions with shared elements.
1. Hero Navigation
Smooth transitions between screens.
// First screen
class ProductListScreen extends StatelessWidget {
final List<Product> products;
const ProductListScreen({required this.products});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Products')),
body: ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
final product = products[index];
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductDetailScreen(product: product),
),
);
},
child: Hero(
tag: 'product_${product.id}',
child: ProductCard(product: product),
),
);
},
),
);
}
}
// Second screen
class ProductDetailScreen extends StatelessWidget {
final Product product;
const ProductDetailScreen({required this.product});
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
expandedHeight: 300,
flexibleSpace: FlexibleSpaceBar(
background: Hero(
tag: 'product_${product.id}',
child: Image.network(
product.imageUrl,
fit: BoxFit.cover,
),
),
),
),
SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
product.name,
style: Theme.of(context).textTheme.headlineMedium,
),
SizedBox(height: 8),
Text(product.description),
// ... more content
],
),
),
),
],
),
);
}
}2. Custom Hero Animation
Custom transition effects.
class CustomHero extends StatefulWidget {
final Widget child;
final String tag;
final Duration duration;
const CustomHero({
required this.child,
required this.tag,
this.duration = const Duration(milliseconds: 600),
});
@override
_CustomHeroState createState() => _CustomHeroState();
}
class _CustomHeroState extends State<CustomHero>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scaleAnimation;
late Animation<double> _fadeAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: widget.duration,
vsync: this,
);
_scaleAnimation = Tween<double>(
begin: 0.8,
end: 1.0,
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.elasticOut,
));
_fadeAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _controller,
curve: Interval(0.3, 1.0, curve: Curves.easeOut),
));
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.scale(
scale: _scaleAnimation.value,
child: Opacity(
opacity: _fadeAnimation.value,
child: child,
),
);
},
child: widget.child,
);
}
}Performance Tips
1. Optimized Animations
Keep animations at 60fps.
class OptimizedAnimation extends StatefulWidget {
@override
_OptimizedAnimationState createState() => _OptimizedAnimationState();
}
class _OptimizedAnimationState extends State<OptimizedAnimation>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
Widget build(BuildContext context) {
return RepaintBoundary( // Isolate repaints
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
// Only rebuild what's necessary
return Transform.rotate(
angle: _controller.value * 2 * math.pi,
child: child, // Pass child to avoid rebuilding
);
},
child: const Icon(Icons.refresh), // const widget
),
);
}
}2. Animation Curves
Choose appropriate easing functions.
// Common curves and their use cases
final curves = {
'ease': Curves.ease, // Gentle acceleration and deceleration
'easeIn': Curves.easeIn, // Slow start, fast end
'easeOut': Curves.easeOut, // Fast start, slow end
'easeInOut': Curves.easeInOut, // Slow start and end, fast middle
'bounceIn': Curves.bounceIn, // Bouncy entrance
'elasticOut': Curves.elasticOut, // Elastic exit
'fastOutSlowIn': Curves.fastOutSlowIn, // Material design
};Flutter Performance Optimization
Rendering Performance
Keep your app at 60fps.
1. Widget Rebuild Optimization
Minimize unnecessary rebuilds.
// ❌ BAD: Rebuilds entire widget tree
class BadCounter extends StatefulWidget {
@override
_BadCounterState createState() => _BadCounterState();
}
class _BadCounterState extends State<BadCounter> {
int _counter = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Count: $_counter'), // Rebuilds every time
Text('Static text'), // Also rebuilds unnecessarily
ElevatedButton(
onPressed: () => setState(() => _counter++),
child: Text('Increment'),
),
],
);
}
}
// ✅ GOOD: Separate static and dynamic parts
class GoodCounter extends StatelessWidget {
final int counter;
final VoidCallback onIncrement;
const GoodCounter({required this.counter, required this.onIncrement});
@override
Widget build(BuildContext context) {
return Column(
children: [
_CounterDisplay(counter: counter), // Only rebuilds counter
const StaticText(), // Never rebuilds
ElevatedButton(
onPressed: onIncrement,
child: const Text('Increment'), // const widget
),
],
);
}
}
class _CounterDisplay extends StatelessWidget {
final int counter;
const _CounterDisplay({required this.counter});
@override
Widget build(BuildContext context) {
return Text('Count: $counter');
}
}
class StaticText extends StatelessWidget {
const StaticText();
@override
Widget build(BuildContext context) {
return const Text('Static text'); // const constructor
}
}2. Const Constructors
Use const wherever possible.
// ❌ BAD: Creates new instances every build
Widget build(BuildContext context) {
return Column(
children: [
Icon(Icons.add),
Text('Hello'),
SizedBox(height: 16),
Padding(padding: EdgeInsets.all(8)),
],
);
}
// ✅ GOOD: Reuses const instances
Widget build(BuildContext context) {
return const Column(
children: [
Icon(Icons.add),
Text('Hello'),
SizedBox(height: 16),
Padding(padding: EdgeInsets.all(8)),
],
);
}3. RepaintBoundary
Isolate expensive repaints.
class ExpensiveWidget extends StatefulWidget {
@override
_ExpensiveWidgetState createState() => _ExpensiveWidgetState();
}
class _ExpensiveWidgetState extends State<ExpensiveWidget>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
Widget build(BuildContext context) {
return RepaintBoundary( // Isolate this widget's repaints
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return CustomPaint(
painter: ExpensivePainter(_controller.value),
child: child,
);
},
child: Container(), // Static child doesn't repaint
),
);
}
}
class ExpensiveList extends StatelessWidget {
final List<Item> items;
const ExpensiveList({required this.items});
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return RepaintBoundary( // Isolate each list item
child: ExpensiveListItem(item: items[index]),
);
},
);
}
}List Performance
Optimize long lists efficiently.
1. ListView.builder vs ListView
Use builder for long lists.
// ❌ BAD: Builds all items at once
class BadListView extends StatelessWidget {
final List<Item> items;
const BadListView({required this.items});
@override
Widget build(BuildContext context) {
return ListView(
children: items.map((item) => ItemWidget(item: item)).toList(),
);
}
}
// ✅ GOOD: Builds items on demand
class GoodListView extends StatelessWidget {
final List<Item> items;
const GoodListView({required this.items});
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ItemWidget(
key: ValueKey(items[index].id), // Important for performance
item: items[index],
);
},
);
}
}2. Item Extent Caching
Cache item dimensions for smooth scrolling.
class OptimizedListView extends StatelessWidget {
final List<Item> items;
const OptimizedListView({required this.items});
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemExtent: 80.0, // Fixed height for each item
itemBuilder: (context, index) {
return ItemWidget(item: items[index]);
},
);
}
}
// Variable height with prototype
class VariableHeightListView extends StatelessWidget {
final List<Item> items;
const VariableHeightListView({required this.items});
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
prototypeItem: ItemWidget(item: items.first), // Prototype for measurement
itemBuilder: (context, index) {
return ItemWidget(item: items[index]);
},
);
}
}3. Slivers for Advanced Lists
Custom scrolling behavior.
class SliverListExample extends StatelessWidget {
final List<Item> items;
const SliverListExample({required this.items});
@override
Widget build(BuildContext context) {
return CustomScrollView(
slivers: [
SliverAppBar(
expandedHeight: 200,
floating: true,
flexibleSpace: FlexibleSpaceBar(
title: Text('Sliver List'),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return ItemWidget(item: items[index]);
},
childCount: items.length,
),
),
SliverFillRemaining(
hasScrollBody: false,
child: Center(child: Text('End of list')),
),
],
);
}
}Image Performance
Optimize image loading and caching.
1. Image Optimization
Efficient image handling.
class OptimizedImage extends StatelessWidget {
final String imageUrl;
final double? width;
final double? height;
const OptimizedImage({
required this.imageUrl,
this.width,
this.height,
});
@override
Widget build(BuildContext context) {
return Image.network(
imageUrl,
width: width,
height: height,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
);
},
errorBuilder: (context, error, stackTrace) {
return Container(
color: Colors.grey[300],
child: Icon(Icons.error),
);
},
cacheWidth: width?.toInt(), // Cache at target size
cacheHeight: height?.toInt(),
filterQuality: FilterQuality.medium, // Balance quality and performance
);
}
}
// Cached network image
class CachedImage extends StatelessWidget {
final String imageUrl;
final double? width;
final double? height;
const CachedImage({
required this.imageUrl,
this.width,
this.height,
});
@override
Widget build(BuildContext context) {
return CachedNetworkImage(
imageUrl: imageUrl,
width: width,
height: height,
placeholder: (context, url) => Container(
color: Colors.grey[300],
child: CircularProgressIndicator(),
),
errorWidget: (context, url, error) => Container(
color: Colors.grey[300],
child: Icon(Icons.error),
),
memCacheWidth: width?.toInt(),
memCacheHeight: height?.toInt(),
);
}
}2. Image Memory Management
Prevent memory leaks.
class ImageGallery extends StatefulWidget {
final List<String> imageUrls;
const ImageGallery({required this.imageUrls});
@override
_ImageGalleryState createState() => _ImageGalleryState();
}
class _ImageGalleryState extends State<ImageGallery> {
final Map<String, ImageProvider> _imageCache = {};
@override
void dispose() {
// Clear image cache
for (final imageProvider in _imageCache.values) {
imageProvider.evict();
}
super.dispose();
}
ImageProvider _getImageProvider(String url) {
return _imageCache.putIfAbsent(
url,
() => NetworkImage(url),
);
}
@override
Widget build(BuildContext context) {
return PageView.builder(
itemCount: widget.imageUrls.length,
itemBuilder: (context, index) {
return Image(
image: _getImageProvider(widget.imageUrls[index]),
fit: BoxFit.cover,
);
},
);
}
}Animation Performance
Smooth 60fps animations.
1. Animation Optimization
Efficient animation patterns.
// ❌ BAD: Animates expensive widgets
class BadAnimation extends StatefulWidget {
@override
_BadAnimationState createState() => _BadAnimationState();
}
class _BadAnimationState extends State<BadAnimation>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Column( // Rebuilds entire column
children: [
Text('Expensive widget 1'),
Text('Expensive widget 2'),
Text('Expensive widget 3'),
Transform.rotate( // Only this needs animation
angle: _controller.value * 2 * math.pi,
child: Icon(Icons.refresh),
),
],
);
},
);
}
}
// ✅ GOOD: Isolate animated parts
class GoodAnimation extends StatefulWidget {
@override
_GoodAnimationState createState() => _GoodAnimationState();
}
class _GoodAnimationState extends State<GoodAnimation>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
Widget build(BuildContext context) {
return Column(
children: [
const Text('Static widget 1'), // const, no rebuild
const Text('Static widget 2'), // const, no rebuild
const Text('Static widget 3'), // const, no rebuild
AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.rotate(
angle: _controller.value * 2 * math.pi,
child: child, // Pass child to avoid rebuilding
);
},
child: const Icon(Icons.refresh), // const widget
),
],
);
}
}2. Performance-Friendly Animations
Choose the right animation type.
// Use implicit animations for simple cases
class SimpleAnimation extends StatefulWidget {
final bool isVisible;
const SimpleAnimation({required this.isVisible});
@override
Widget build(BuildContext context) {
return AnimatedOpacity(
opacity: isVisible ? 1.0 : 0.0,
duration: Duration(milliseconds: 200),
curve: Curves.easeOut,
child: Container(
color: Colors.blue,
height: 100,
width: 100,
),
);
}
}
// Use explicit animations for complex cases
class ComplexAnimation extends StatefulWidget {
@override
_ComplexAnimationState createState() => _ComplexAnimationState();
}
class _ComplexAnimationState extends State<ComplexAnimation>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scaleAnimation;
late Animation<Color?> _colorAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(milliseconds: 800),
vsync: this,
);
_scaleAnimation = Tween<double>(
begin: 1.0,
end: 1.2,
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
));
_colorAnimation = ColorTween(
begin: Colors.blue,
end: Colors.red,
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
));
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.scale(
scale: _scaleAnimation.value,
child: Container(
color: _colorAnimation.value,
height: 100,
width: 100,
),
);
},
);
}
}Memory Management
Prevent memory leaks and bloat.
1. Controller Management
Proper disposal of controllers.
class ProperControllerManagement extends StatefulWidget {
@override
_ProperControllerManagementState createState() =>
_ProperControllerManagementState();
}
class _ProperControllerManagementState
extends State<ProperControllerManagement>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late TextEditingController _textController;
late ScrollController _scrollController;
late PageController _pageController;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: Duration(milliseconds: 300),
vsync: this,
);
_textController = TextEditingController();
_scrollController = ScrollController();
_pageController = PageController();
}
@override
void dispose() {
// Always dispose controllers
_animationController.dispose();
_textController.dispose();
_scrollController.dispose();
_pageController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Transform.rotate(
angle: _animationController.value * 2 * math.pi,
child: child,
);
},
child: Icon(Icons.refresh),
),
TextField(controller: _textController),
Expanded(
child: ListView.builder(
controller: _scrollController,
itemCount: 100,
itemBuilder: (context, index) {
return ListTile(title: Text('Item $index'));
},
),
),
PageView.builder(
controller: _pageController,
itemCount: 3,
itemBuilder: (context, index) {
return Center(child: Text('Page $index'));
},
),
],
);
}
}2. Stream Management
Proper stream subscription handling.
class StreamManagement extends StatefulWidget {
@override
_StreamManagementState createState() => _StreamManagementState();
}
class _StreamManagementState extends State<StreamManagement> {
final StreamController<String> _streamController = StreamController<String>();
late StreamSubscription _subscription;
int _counter = 0;
@override
void initState() {
super.initState();
_subscription = _streamController.stream.listen((data) {
setState(() {
_counter++;
});
});
// Start emitting data
Timer.periodic(Duration(seconds: 1), (timer) {
_streamController.add('Data ${timer.tick}');
});
}
@override
void dispose() {
// Always cancel subscriptions
_subscription.cancel();
_streamController.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Counter: $_counter'),
ElevatedButton(
onPressed: () {
_streamController.add('Manual data');
},
child: Text('Add Data'),
),
],
);
}
}Performance Profiling
Use Flutter DevTools effectively.
1. Performance Overlay
Built-in performance debugging.
class PerformanceApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
// Enable performance overlay
checkerboardRasterCacheImages: true, // Show raster cache
checkerboardOffscreenLayers: true, // Show offscreen layers
showPerformanceOverlay: true, // Show performance metrics
home: MyHomePage(),
);
}
}2. Custom Performance Monitor
Track app performance.
class PerformanceMonitor extends StatefulWidget {
final Widget child;
const PerformanceMonitor({required this.child});
@override
_PerformanceMonitorState createState() => _PerformanceMonitorState();
}
class _PerformanceMonitorState extends State<PerformanceMonitor>
with WidgetsBindingObserver {
final List<Duration> _frameTimes = [];
Timer? _timer;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_startMonitoring();
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
_timer?.cancel();
super.dispose();
}
void _startMonitoring() {
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
if (_frameTimes.isNotEmpty) {
final averageFrameTime = _frameTimes.reduce(
(a, b) => a + b,
) / _frameTimes.length;
final fps = 1000 / averageFrameTime.inMilliseconds;
print('Average FPS: ${fps.toStringAsFixed(1)}');
_frameTimes.clear();
}
});
}
@override
void didHaveMetrics(Duration frameTime) {
_frameTimes.add(frameTime);
}
@override
Widget build(BuildContext context) {
return widget.child;
}
}Best Practices Summary
DO ✅
- Use
constconstructors for static widgets - Implement
RepaintBoundaryfor complex animations - Use
ListView.builderfor long lists - Dispose controllers and subscriptions properly
- Cache image providers and dimensions
- Profile with Flutter DevTools regularly
DON'T ❌
- Rebuild entire widget trees unnecessarily
- Create new instances in build methods
- Use regular
ListViewfor long lists - Forget to dispose controllers
- Load full-resolution images for thumbnails
- Ignore performance warnings
Performance Checklist
- [ ] All static widgets use
constconstructors - [ ] Expensive widgets wrapped in
RepaintBoundary - [ ] Lists use
builderconstructors - [ ] Controllers properly disposed
- [ ] Images optimized with caching
- [ ] Animations run at 60fps
- [ ] Memory usage is stable
- [ ] No jank during scrolling
Flutter Theme Templates
Material Design 3 Themes
Modern Material You implementation.
1. Dynamic Color Theme
Adaptive colors based on system preferences.
class AppTheme {
static ThemeData lightTheme(ColorScheme? dynamicColorScheme) {
final colorScheme = dynamicColorScheme ??
ColorScheme.fromSeed(
seedColor: Colors.blue,
brightness: Brightness.light,
);
return ThemeData(
useMaterial3: true,
colorScheme: colorScheme,
brightness: Brightness.light,
// Typography
textTheme: _buildTextTheme(colorScheme),
// Component themes
elevatedButtonTheme: _elevatedButtonTheme(colorScheme),
cardTheme: _cardTheme(colorScheme),
appBarTheme: _appBarTheme(colorScheme),
bottomNavigationBarTheme: _bottomNavTheme(colorScheme),
);
}
static ThemeData darkTheme(ColorScheme? dynamicColorScheme) {
final colorScheme = dynamicColorScheme ??
ColorScheme.fromSeed(
seedColor: Colors.blue,
brightness: Brightness.dark,
);
return ThemeData(
useMaterial3: true,
colorScheme: colorScheme,
brightness: Brightness.dark,
textTheme: _buildTextTheme(colorScheme),
elevatedButtonTheme: _elevatedButtonTheme(colorScheme),
cardTheme: _cardTheme(colorScheme),
appBarTheme: _appBarTheme(colorScheme),
bottomNavigationBarTheme: _bottomNavTheme(colorScheme),
);
}
static TextTheme _buildTextTheme(ColorScheme colorScheme) {
return TextTheme(
displayLarge: TextStyle(
fontSize: 57,
fontWeight: FontWeight.w400,
color: colorScheme.onSurface,
),
displayMedium: TextStyle(
fontSize: 45,
fontWeight: FontWeight.w400,
color: colorScheme.onSurface,
),
headlineLarge: TextStyle(
fontSize: 32,
fontWeight: FontWeight.w400,
color: colorScheme.onSurface,
),
headlineMedium: TextStyle(
fontSize: 28,
fontWeight: FontWeight.w400,
color: colorScheme.onSurface,
),
titleLarge: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w500,
color: colorScheme.onSurface,
),
titleMedium: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: colorScheme.onSurface,
),
bodyLarge: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
color: colorScheme.onSurface,
),
bodyMedium: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
color: colorScheme.onSurface,
),
labelLarge: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: colorScheme.onPrimary,
),
);
}
static ElevatedButtonThemeData _elevatedButtonTheme(ColorScheme colorScheme) {
return ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: colorScheme.primary,
foregroundColor: colorScheme.onPrimary,
elevation: 2,
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
);
}
static CardTheme _cardTheme(ColorScheme colorScheme) {
return CardTheme(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
color: colorScheme.surface,
);
}
static AppBarTheme _appBarTheme(ColorScheme colorScheme) {
return AppBarTheme(
backgroundColor: colorScheme.surface,
foregroundColor: colorScheme.onSurface,
elevation: 0,
centerTitle: true,
titleTextStyle: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w500,
color: colorScheme.onSurface,
),
);
}
static BottomNavigationBarThemeData _bottomNavTheme(ColorScheme colorScheme) {
return BottomNavigationBarThemeData(
backgroundColor: colorScheme.surface,
selectedItemColor: colorScheme.primary,
unselectedItemColor: colorScheme.onSurfaceVariant,
type: BottomNavigationBarType.fixed,
elevation: 8,
landscapeLayout: BottomNavigationBarLandscapeLayout.centered,
);
}
}2. Brand Theme
Custom brand colors and styling.
class BrandTheme {
// Brand colors
static const Color primaryBrand = Color(0xFF2E7D32); // Green
static const Color secondaryBrand = Color(0xFF1976D2); // Blue
static const Color accentBrand = Color(0xFFFF6F00); // Orange
static const Color lightBackground = Color(0xFFF5F5F5);
static const Color darkBackground = Color(0xFF121212);
static ThemeData lightBrandTheme() {
final colorScheme = ColorScheme.fromSeed(
seedColor: primaryBrand,
brightness: Brightness.light,
primary: primaryBrand,
secondary: secondaryBrand,
tertiary: accentBrand,
);
return ThemeData(
useMaterial3: true,
colorScheme: colorScheme,
// Custom extensions
extensions: [
_BrandColors(
primary: primaryBrand,
secondary: secondaryBrand,
accent: accentBrand,
background: lightBackground,
),
],
// Component themes
elevatedButtonTheme: _brandElevatedButton(colorScheme),
outlinedButtonTheme: _brandOutlinedButton(colorScheme),
textButtonTheme: _brandTextButton(colorScheme),
inputDecorationTheme: _brandInputDecoration(colorScheme),
);
}
static ThemeData darkBrandTheme() {
final colorScheme = ColorScheme.fromSeed(
seedColor: primaryBrand,
brightness: Brightness.dark,
primary: primaryBrand,
secondary: secondaryBrand,
tertiary: accentBrand,
);
return ThemeData(
useMaterial3: true,
colorScheme: colorScheme,
extensions: [
_BrandColors(
primary: primaryBrand,
secondary: secondaryBrand,
accent: accentBrand,
background: darkBackground,
),
],
elevatedButtonTheme: _brandElevatedButton(colorScheme),
outlinedButtonTheme: _brandOutlinedButton(colorScheme),
textButtonTheme: _brandTextButton(colorScheme),
inputDecorationTheme: _brandInputDecoration(colorScheme),
);
}
static ElevatedButtonThemeData _brandElevatedButton(ColorScheme colorScheme) {
return ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: primaryBrand,
foregroundColor: Colors.white,
elevation: 4,
shadowColor: primaryBrand.withOpacity(0.3),
padding: EdgeInsets.symmetric(horizontal: 32, vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
);
}
static OutlinedButtonThemeData _brandOutlinedButton(ColorScheme colorScheme) {
return OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: primaryBrand,
side: BorderSide(color: primaryBrand, width: 2),
padding: EdgeInsets.symmetric(horizontal: 32, vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
);
}
static TextButtonThemeData _brandTextButton(ColorScheme colorScheme) {
return TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: primaryBrand,
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
);
}
static InputDecorationTheme _brandInputDecoration(ColorScheme colorScheme) {
return InputDecorationTheme(
filled: true,
fillColor: colorScheme.surface,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: colorScheme.outline),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: primaryBrand, width: 2),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: colorScheme.error, width: 2),
),
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
);
}
}
// Custom theme extension
@immutable
class _BrandColors extends ThemeExtension<_BrandColors> {
final Color primary;
final Color secondary;
final Color accent;
final Color background;
const _BrandColors({
required this.primary,
required this.secondary,
required this.accent,
required this.background,
});
@override
_BrandColors copyWith({
Color? primary,
Color? secondary,
Color? accent,
Color? background,
}) {
return _BrandColors(
primary: primary ?? this.primary,
secondary: secondary ?? this.secondary,
accent: accent ?? this.accent,
background: background ?? this.background,
);
}
@override
_BrandColors lerp(ThemeExtension<_BrandColors>? other, double t) {
if (other is! _BrandColors) return this;
return _BrandColors(
primary: Color.lerp(primary, other.primary, t)!,
secondary: Color.lerp(secondary, other.secondary, t)!,
accent: Color.lerp(accent, other.accent, t)!,
background: Color.lerp(background, other.background, t)!,
);
}
}Custom Theme System
Advanced theming with multiple variations.
1. Multi-Theme System
Switch between different visual themes.
enum AppThemeType { light, dark, blue, green, purple }
class ThemeManager extends ChangeNotifier {
AppThemeType _currentTheme = AppThemeType.light;
ThemeMode _themeMode = ThemeMode.system;
AppThemeType get currentTheme => _currentTheme;
ThemeMode get themeMode => _themeMode;
void setTheme(AppThemeType theme) {
_currentTheme = theme;
notifyListeners();
}
void setThemeMode(ThemeMode mode) {
_themeMode = mode;
notifyListeners();
}
ThemeData get lightTheme => _getThemeData(AppThemeType.light, Brightness.light);
ThemeData get darkTheme => _getThemeData(AppThemeType.dark, Brightness.dark);
ThemeData _getThemeData(AppThemeType themeType, Brightness brightness) {
switch (themeType) {
case AppThemeType.light:
return _buildLightTheme(brightness);
case AppThemeType.dark:
return _buildDarkTheme(brightness);
case AppThemeType.blue:
return _buildBlueTheme(brightness);
case AppThemeType.green:
return _buildGreenTheme(brightness);
case AppThemeType.purple:
return _buildPurpleTheme(brightness);
}
}
ThemeData _buildLightTheme(Brightness brightness) {
final colorScheme = ColorScheme.fromSeed(
seedColor: Colors.blue,
brightness: brightness,
);
return ThemeData(
useMaterial3: true,
colorScheme: colorScheme,
appBarTheme: AppBarTheme(
backgroundColor: colorScheme.surface,
elevation: 0,
),
);
}
ThemeData _buildBlueTheme(Brightness brightness) {
final colorScheme = ColorScheme.fromSeed(
seedColor: Colors.blue,
brightness: brightness,
);
return ThemeData(
useMaterial3: true,
colorScheme: colorScheme,
appBarTheme: AppBarTheme(
backgroundColor: colorScheme.primaryContainer,
foregroundColor: colorScheme.onPrimaryContainer,
),
);
}
ThemeData _buildGreenTheme(Brightness brightness) {
final colorScheme = ColorScheme.fromSeed(
seedColor: Colors.green,
brightness: brightness,
);
return ThemeData(
useMaterial3: true,
colorScheme: colorScheme,
appBarTheme: AppBarTheme(
backgroundColor: colorScheme.primary,
foregroundColor: colorScheme.onPrimary,
),
);
}
ThemeData _buildPurpleTheme(Brightness brightness) {
final colorScheme = ColorScheme.fromSeed(
seedColor: Colors.purple,
brightness: brightness,
);
return ThemeData(
useMaterial3: true,
colorScheme: colorScheme,
appBarTheme: AppBarTheme(
backgroundColor: colorScheme.surfaceVariant,
foregroundColor: colorScheme.onSurfaceVariant,
),
);
}
}
// Usage with Provider
class ThemeProvider extends StatelessWidget {
final Widget child;
const ThemeProvider({required this.child});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => ThemeManager(),
child: Consumer<ThemeManager>(
builder: (context, themeManager, child) {
return MaterialApp(
theme: themeManager.lightTheme,
darkTheme: themeManager.darkTheme,
themeMode: themeManager.themeMode,
child: child!,
);
},
child: child,
),
);
}
}2. Theme Switcher Widget
UI for switching themes.
class ThemeSwitcher extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer<ThemeManager>(
builder: (context, themeManager, child) {
return PopupMenuButton<AppThemeType>(
icon: Icon(Icons.palette),
onSelected: (theme) => themeManager.setTheme(theme),
itemBuilder: (context) => [
PopupMenuItem(
value: AppThemeType.light,
child: Row(
children: [
Icon(Icons.light_mode, color: Colors.amber),
SizedBox(width: 8),
Text('Light'),
],
),
),
PopupMenuItem(
value: AppThemeType.dark,
child: Row(
children: [
Icon(Icons.dark_mode, color: Colors.blueGrey),
SizedBox(width: 8),
Text('Dark'),
],
),
),
PopupMenuItem(
value: AppThemeType.blue,
child: Row(
children: [
Icon(Icons.circle, color: Colors.blue),
SizedBox(width: 8),
Text('Blue'),
],
),
),
PopupMenuItem(
value: AppThemeType.green,
child: Row(
children: [
Icon(Icons.circle, color: Colors.green),
SizedBox(width: 8),
Text('Green'),
],
),
),
PopupMenuItem(
value: AppThemeType.purple,
child: Row(
children: [
Icon(Icons.circle, color: Colors.purple),
SizedBox(width: 8),
Text('Purple'),
],
),
),
],
);
},
);
}
}Responsive Theme Adaptation
Adapt themes for different screen sizes.
1. Responsive Theme Builder
Different themes for different screen sizes.
class ResponsiveTheme extends StatelessWidget {
final Widget child;
const ResponsiveTheme({required this.child});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
ThemeData theme;
if (constraints.maxWidth < 600) {
// Mobile theme
theme = _buildMobileTheme();
} else if (constraints.maxWidth < 1200) {
// Tablet theme
theme = _buildTabletTheme();
} else {
// Desktop theme
theme = _buildDesktopTheme();
}
return Theme(
data: theme,
child: child,
);
},
);
}
ThemeData _buildMobileTheme() {
return ThemeData(
useMaterial3: true,
textTheme: TextTheme(
headlineLarge: TextStyle(fontSize: 24), // Smaller for mobile
bodyLarge: TextStyle(fontSize: 16),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), // Compact
),
),
);
}
ThemeData _buildTabletTheme() {
return ThemeData(
useMaterial3: true,
textTheme: TextTheme(
headlineLarge: TextStyle(fontSize: 32), // Medium
bodyLarge: TextStyle(fontSize: 18),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
),
),
);
}
ThemeData _buildDesktopTheme() {
return ThemeData(
useMaterial3: true,
textTheme: TextTheme(
headlineLarge: TextStyle(fontSize: 40), // Larger for desktop
bodyLarge: TextStyle(fontSize: 20),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
padding: EdgeInsets.symmetric(horizontal: 32, vertical: 16), // Spacious
),
),
);
}
}Theme Usage Examples
1. Access Theme in Widgets
Proper theme access patterns.
class ThemedWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
final textTheme = theme.textTheme;
return Container(
color: colorScheme.primary,
child: Text(
'Themed Text',
style: textTheme.headlineMedium?.copyWith(
color: colorScheme.onPrimary,
),
),
);
}
}
// Using custom theme extensions
class CustomThemedWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final brandColors = Theme.of(context).extension<_BrandColors>();
return Container(
color: brandColors?.primary ?? Colors.blue,
child: Text(
'Brand Themed',
style: TextStyle(color: brandColors != null ? Colors.white : null),
),
);
}
}2. Theme Switching Animation
Smooth theme transitions.
class AnimatedThemeSwitch extends StatefulWidget {
final Widget child;
const AnimatedThemeSwitch({required this.child});
@override
_AnimatedThemeSwitchState createState() => _AnimatedThemeSwitchState();
}
class _AnimatedThemeSwitchState extends State<AnimatedThemeSwitch>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(milliseconds: 300),
vsync: this,
);
_animation = CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
);
}
void switchTheme(ThemeData newTheme) {
_controller.forward().then((_) {
// Update theme
_controller.reverse();
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Opacity(
opacity: 1.0 - _animation.value.abs() * 0.3,
child: child,
);
},
child: widget.child,
);
}
}Flutter Widget Patterns
Composition Patterns
1. Builder Pattern
Use when you need context-dependent widgets or performance optimization.
// LayoutBuilder for responsive design
LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth > 600) {
return _buildDesktopLayout();
} else {
return _buildMobileLayout();
}
},
)
// FutureBuilder for async data
FutureBuilder<String>(
future: _fetchData(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}
return Text(snapshot.data ?? 'No data');
},
)2. State Management Pattern
Choose the right approach for your app complexity.
// Simple state with StatefulWidget
class CounterWidget extends StatefulWidget {
@override
_CounterWidgetState createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _counter = 0;
void _increment() => setState(() => _counter++);
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Count: $_counter'),
ElevatedButton(
onPressed: _increment,
child: Text('Increment'),
),
],
);
}
}
// Complex state with Provider/Bloc
class CounterProvider extends ChangeNotifier {
int _counter = 0;
int get counter => _counter;
void increment() {
_counter++;
notifyListeners();
}
}
// Usage
ChangeNotifierProvider(
create: (_) => CounterProvider(),
child: Consumer<CounterProvider>(
builder: (context, provider, child) {
return Text('Count: ${provider.counter}');
},
),
)3. Custom Widget Pattern
Create reusable, themed components.
class CustomCard extends StatelessWidget {
final Widget child;
final EdgeInsets? padding;
final Color? backgroundColor;
final double? elevation;
const CustomCard({
required this.child,
this.padding,
this.backgroundColor,
this.elevation,
});
@override
Widget build(BuildContext context) {
return Card(
elevation: elevation ?? 4,
color: backgroundColor ?? Theme.of(context).cardColor,
child: Padding(
padding: padding ?? const EdgeInsets.all(16),
child: child,
),
);
}
}
// Usage with theme integration
class ThemedCard extends StatelessWidget {
final Widget child;
final EdgeInsets? padding;
const ThemedCard({required this.child, this.padding});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Theme.of(context).shadowColor.withOpacity(0.1),
blurRadius: 8,
offset: Offset(0, 2),
),
],
),
child: Padding(
padding: padding ?? const EdgeInsets.all(16),
child: child,
),
);
}
}Layout Patterns
1. Responsive Grid
Adaptive grid layouts for different screen sizes.
class ResponsiveGrid extends StatelessWidget {
final List<Widget> children;
final int mobileColumns;
final int tabletColumns;
final int desktopColumns;
const ResponsiveGrid({
required this.children,
this.mobileColumns = 1,
this.tabletColumns = 2,
this.desktopColumns = 3,
});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
int columns;
if (constraints.maxWidth < 600) {
columns = mobileColumns;
} else if (constraints.maxWidth < 1200) {
columns = tabletColumns;
} else {
columns = desktopColumns;
}
return GridView.count(
crossAxisCount: columns,
children: children,
);
},
);
}
}2. Expandable Section
Collapsible content with smooth animations.
class ExpandableSection extends StatefulWidget {
final Widget title;
final Widget content;
final bool initiallyExpanded;
const ExpandableSection({
required this.title,
required this.content,
this.initiallyExpanded = false,
});
@override
_ExpandableSectionState createState() => _ExpandableSectionState();
}
class _ExpandableSectionState extends State<ExpandableSection>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
bool _isExpanded = false;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(milliseconds: 300),
vsync: this,
);
_animation = CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
);
if (widget.initiallyExpanded) {
_isExpanded = true;
_controller.value = 1.0;
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _toggle() {
setState(() {
_isExpanded = !_isExpanded;
if (_isExpanded) {
_controller.forward();
} else {
_controller.reverse();
}
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
GestureDetector(
onTap: _toggle,
child: Container(
padding: EdgeInsets.all(16),
child: Row(
children: [
Expanded(child: widget.title),
AnimatedRotation(
turns: _isExpanded ? 0.5 : 0,
duration: Duration(milliseconds: 300),
child: Icon(Icons.expand_more),
),
],
),
),
),
SizeTransition(
sizeFactor: _animation,
child: widget.content,
),
],
);
}
}Interaction Patterns
1. Swipeable List Item
Gestures for list interactions.
class SwipeableListItem extends StatefulWidget {
final Widget child;
final VoidCallback? onSwipeLeft;
final VoidCallback? onSwipeRight;
final Widget? leftAction;
final Widget? rightAction;
const SwipeableListItem({
required this.child,
this.onSwipeLeft,
this.onSwipeRight,
this.leftAction,
this.rightAction,
});
@override
_SwipeableListItemState createState() => _SwipeableListItemState();
}
class _SwipeableListItemState extends State<SwipeableListItem>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(milliseconds: 200),
vsync: this,
);
_animation = CurvedAnimation(
parent: _controller,
curve: Curves.easeOut,
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onPanUpdate: (details) {
if (details.delta.dx > 0 && widget.onSwipeRight != null) {
_controller.forward();
} else if (details.delta.dx < 0 && widget.onSwipeLeft != null) {
_controller.forward();
}
},
onPanEnd: (details) {
if (_controller.value > 0.5) {
if (details.velocity.pixelsPerSecond.dx > 0) {
widget.onSwipeRight?.call();
} else {
widget.onSwipeLeft?.call();
}
}
_controller.reverse();
},
child: Stack(
children: [
widget.child,
if (widget.leftAction != null)
Positioned.fill(
child: AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Transform.translate(
offset: Offset(_animation.value * -100, 0),
child: widget.leftAction,
);
},
),
),
if (widget.rightAction != null)
Positioned.fill(
child: AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Transform.translate(
offset: Offset(_animation.value * 100, 0),
child: widget.rightAction,
);
},
),
),
],
),
);
}
}2. Pull to Refresh
Custom pull-to-refresh implementation.
class CustomPullToRefresh extends StatefulWidget {
final Future<void> Function() onRefresh;
final Widget child;
final double refreshTriggerOffset;
const CustomPullToRefresh({
required this.onRefresh,
required this.child,
this.refreshTriggerOffset = 80.0,
});
@override
_CustomPullToRefreshState createState() => _CustomPullToRefreshState();
}
class _CustomPullToRefreshState extends State<CustomPullToRefresh>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
bool _isRefreshing = false;
double _dragOffset = 0.0;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(milliseconds: 300),
vsync: this,
);
_animation = CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
Future<void> _handleRefresh() async {
setState(() => _isRefreshing = true);
await widget.onRefresh();
setState(() => _isRefreshing = false);
_controller.reverse();
}
@override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: _handleRefresh,
child: CustomScrollView(
physics: AlwaysScrollableScrollPhysics(),
slivers: [
SliverToBoxAdapter(
child: AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Transform.translate(
offset: Offset(0, _animation.value * _dragOffset),
child: child,
);
},
child: widget.child,
),
),
],
),
);
}
}Performance Patterns
1. ListView Optimization
Efficient list rendering with builders.
class OptimizedListView extends StatelessWidget {
final List<Item> items;
const OptimizedListView({required this.items});
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return _buildItem(items[index]);
},
// Use const keys for better performance
key: ValueKey('list_${items.length}'),
);
}
Widget _buildItem(Item item) {
return Card(
key: ValueKey(item.id), // Unique key for each item
child: ListTile(
title: Text(item.title),
subtitle: Text(item.description),
// Use const for static widgets
leading: const Icon(Icons.article),
),
);
}
}2. RepaintBoundary
Isolate expensive widget rebuilds.
class ComplexAnimation extends StatefulWidget {
@override
_ComplexAnimationState createState() => _ComplexAnimationState();
}
class _ComplexAnimationState extends State<ComplexAnimation>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
Widget build(BuildContext context) {
return RepaintBoundary(
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return CustomPaint(
painter: ComplexPainter(_controller.value),
child: child,
);
},
child: Container(), // Static child doesn't rebuild
),
);
}
}Related skills
How it compares
Choose flutter-ui-ux for Flutter-specific UI and motion work rather than generic mobile design advice.
FAQ
What does flutter-ui-ux help build?
flutter-ui-ux helps developers build Flutter UI components and screens, implement animations and transitions, design responsive layouts, create custom widgets and themes, and improve UI performance and accessibility.
When should flutter-ui-ux activate?
flutter-ui-ux activates when users request Flutter UI creation, screen builds, animations, responsive layouts, custom widgets, or theme design using its documented trigger phrases.