| name | custom-plugin-flutter-skill-ui |
| description | 1700+ lines of Flutter UI mastery - widgets, layouts, Material Design, animations, responsive design with production-ready code examples and enterprise patterns. |
| sasmp_version | 1.3.0 |
| bonded_agent | 01-flutter-ui-development |
| bond_type | PRIMARY_BOND |
custom-plugin-flutter: UI Development Skill
Quick Start - Production UI Pattern
class ResponsiveProductScreen extends ConsumerWidget {
const ResponsiveProductScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final isMobile = MediaQuery.of(context).size.width < 600;
final productState = ref.watch(productProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Products'),
elevation: 0,
),
body: productState.when(
loading: () => const LoadingWidget(),
data: (products) => isMobile
? _MobileProductList(products: products)
: _DesktopProductGrid(products: products),
error: (error, st) => ErrorWidget(error: error.toString()),
),
);
}
}
class _MobileProductList extends StatelessWidget {
final List<Product> products;
const _MobileProductList({required this.products});
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) => ProductCard(product: products[index]),
);
}
}
class _DesktopProductGrid extends StatelessWidget {
final List<Product> products;
const _DesktopProductGrid({required this.products});
@override
Widget build(BuildContext context) {
return GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: 0.8,
),
itemCount: products.length,
itemBuilder: (context, index) => ProductCard(product: products[index]),
);
}
}
1. Widget System Mastery
Understanding the Widget Tree
Stateless Widgets - Pure, immutable widgets:
class PureWidget extends StatelessWidget {
final String title;
const PureWidget({required this.title});
@override
Widget build(BuildContext context) => Text(title);
}
Stateful Widgets - Managing internal state:
class CounterWidget extends StatefulWidget {
const CounterWidget({Key? key}) : super(key: key);
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _count = 0;
void _increment() {
setState(() => _count++);
}
@override
void initState() {
super.initState();
// Initialize resources
}
@override
void dispose() {
// Clean up resources
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Count: $_count'),
ElevatedButton(
onPressed: _increment,
child: const Text('Increment'),
),
],
);
}
}
Const Constructors - Preventing unnecessary rebuilds:
// ✅ Good - Const constructor
const SizedBox(height: 16, child: Text('Hello'))
// ❌ Bad - Non-const, rebuilds every time
SizedBox(height: 16, child: Text('Hello'))
// ✅ Make all widgets const when possible
class MyWidget extends StatelessWidget {
const MyWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) => const Placeholder();
}
Inherited Widgets - Efficient state propagation:
class ThemeProvider extends InheritedWidget {
final ThemeData theme;
const ThemeProvider({
required this.theme,
required super.child,
super.key,
});
static ThemeProvider of(BuildContext context) {
final result = context.dependOnInheritedWidgetOfExactType<ThemeProvider>();
assert(result != null, 'No ThemeProvider found in context');
return result!;
}
@override
bool updateShouldNotify(ThemeProvider oldWidget) => theme != oldWidget.theme;
}
// Usage
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ThemeProvider(
theme: ThemeData.light(),
child: MaterialApp(home: MyScreen()),
);
}
}
class MyScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = ThemeProvider.of(context).theme;
return Scaffold(
backgroundColor: theme.scaffoldBackgroundColor,
);
}
}
2. Constraint-Based Layout System
Understanding Constraints
Basic Constraint Flow:
// Parent imposes constraints on children
// Child sizes itself based on constraints
// Parent positions child based on alignment
Center( // Imposes tight constraint
child: Container(
width: 200,
height: 100,
color: Colors.blue,
),
)
Layout Widgets:
// Row - horizontal layout
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text('Left'),
Text('Middle'),
Text('Right'),
],
)
// Column - vertical layout
Column(
mainAxisSize: MainAxisSize.max,
children: [
Container(height: 100, color: Colors.red),
Container(height: 100, color: Colors.blue),
Container(height: 100, color: Colors.green),
],
)
// Flex - flexible layout
Flex(
direction: Axis.horizontal,
children: [
Flexible(flex: 2, child: Container(color: Colors.red)),
Flexible(flex: 1, child: Container(color: Colors.blue)),
],
)
Advanced Layouts:
// GridView - grid layout
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
),
itemCount: 12,
itemBuilder: (context, index) => Container(
color: Colors.blue[100 * ((index % 9) + 1)],
),
)
// Stack - overlaying widgets
Stack(
alignment: Alignment.bottomRight,
children: [
Image.asset('background.png'),
Positioned(
right: 16,
bottom: 16,
child: FloatingActionButton(onPressed: () {}),
),
],
)
// CustomMultiChildLayout - manual layout
CustomMultiChildLayout(
delegate: MyLayoutDelegate(),
children: [
LayoutId(id: 'title', child: Text('Title')),
LayoutId(id: 'body', child: Text('Body')),
],
)
3. Material Design 3 Implementation
Theme Configuration
class AppTheme {
static ThemeData lightTheme = ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue,
brightness: Brightness.light,
),
typography: Typography.material2021(
platform: defaultTargetPlatform,
),
appBarTheme: AppBarTheme(
elevation: 0,
backgroundColor: Colors.transparent,
),
cardTheme: CardTheme(
elevation: 2,
margin: EdgeInsets.all(16),
),
);
static ThemeData darkTheme = ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue,
brightness: Brightness.dark,
),
);
}
// Usage
MaterialApp(
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
themeMode: ThemeMode.system,
)
Material Components
// Modern AppBar
AppBar(
title: Text('Title'),
elevation: 0,
backgroundColor: Theme.of(context).colorScheme.surface,
foregroundColor: Theme.of(context).colorScheme.onSurface,
)
// Enhanced Button
ElevatedButton.icon(
onPressed: () {},
icon: Icon(Icons.send),
label: Text('Send'),
style: ElevatedButton.styleFrom(
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
)
// Material TextField
TextField(
decoration: InputDecoration(
labelText: 'Enter name',
hintText: 'John Doe',
prefixIcon: Icon(Icons.person),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
filled: true,
fillColor: Colors.grey[100],
),
)
// Material Form
Form(
key: _formKey,
child: Column(
children: [
TextFormField(
validator: (value) {
if (value?.isEmpty ?? true) return 'Required';
return null;
},
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// Submit form
}
},
child: Text('Submit'),
),
],
),
)
4. Animation Framework
AnimationController Pattern
class AnimatedCounterWidget extends StatefulWidget {
const AnimatedCounterWidget();
@override
State<AnimatedCounterWidget> createState() => _AnimatedCounterWidgetState();
}
class _AnimatedCounterWidgetState extends State<AnimatedCounterWidget>
with TickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
int _count = 0;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(milliseconds: 300),
vsync: this,
);
_animation = Tween<double>(begin: 1, end: 0.8).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
);
}
void _increment() {
setState(() => _count++);
_controller.forward().then((_) => _controller.reverse());
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ScaleTransition(
scale: _animation,
child: GestureDetector(
onTap: _increment,
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.blue,
),
child: Center(
child: Text(
'$_count',
style: Theme.of(context).textTheme.headlineLarge?.copyWith(
color: Colors.white,
),
),
),
),
),
);
}
}
Implicit Animations
// AnimatedContainer - animate properties smoothly
class AnimatedBoxWidget extends StatefulWidget {
@override
State<AnimatedBoxWidget> createState() => _AnimatedBoxWidgetState();
}
class _AnimatedBoxWidgetState extends State<AnimatedBoxWidget> {
bool _expanded = false;
@override
Widget build(BuildContext context) {
return Center(
child: GestureDetector(
onTap: () => setState(() => _expanded = !_expanded),
child: AnimatedContainer(
duration: Duration(milliseconds: 500),
width: _expanded ? 300 : 100,
height: _expanded ? 300 : 100,
decoration: BoxDecoration(
color: _expanded ? Colors.blue : Colors.red,
borderRadius: BorderRadius.circular(_expanded ? 16 : 8),
),
child: Center(
child: AnimatedOpacity(
opacity: _expanded ? 1 : 0,
duration: Duration(milliseconds: 500),
child: Text('Expanded'),
),
),
),
),
);
}
}
5. Responsive Design Pattern
class ResponsiveWidget extends StatelessWidget {
final Widget Function(BuildContext) mobileBuilder;
final Widget Function(BuildContext) tabletBuilder;
final Widget Function(BuildContext) desktopBuilder;
const ResponsiveWidget({
required this.mobileBuilder,
required this.tabletBuilder,
required this.desktopBuilder,
});
@override
Widget build(BuildContext context) {
final width = MediaQuery.of(context).size.width;
if (width < 600) {
return mobileBuilder(context);
} else if (width < 1200) {
return tabletBuilder(context);
} else {
return desktopBuilder(context);
}
}
}
// Usage
ResponsiveWidget(
mobileBuilder: (context) => MobileLayout(),
tabletBuilder: (context) => TabletLayout(),
desktopBuilder: (context) => DesktopLayout(),
)
6. Accessibility Best Practices
class AccessibleWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Semantics(
button: true,
label: 'Submit form',
enabled: true,
onTap: () {},
child: Container(
constraints: BoxConstraints(minHeight: 48, minWidth: 48),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(8),
),
child: Center(
child: Text(
'Submit',
style: TextStyle(fontSize: 16), // Respect system font scaling
),
),
),
);
}
}
7. Performance Optimization Tips
- ✅ Use
constconstructors everywhere possible - ✅ Extract widgets to separate classes to limit rebuilds
- ✅ Use
RepaintBoundaryfor expensive rendering - ✅ Use
ListView.builderinstead ofListView - ✅ Cache images with
CachedNetworkImage - ✅ Profile with DevTools regularly
- ✅ Use
LayoutBuilderfor responsive design - ✅ Prefer
SingleChildScrollViewover custom scrolling
8. Custom Widget Template
class CustomButton extends StatelessWidget {
final VoidCallback onPressed;
final String label;
final ButtonSize size;
const CustomButton({
required this.onPressed,
required this.label,
this.size = ButtonSize.medium,
});
@override
Widget build(BuildContext context) {
return Material(
child: InkWell(
onTap: onPressed,
child: Container(
padding: _paddingForSize(size),
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
borderRadius: BorderRadius.circular(8),
),
child: Text(
label,
style: Theme.of(context).textTheme.labelLarge,
),
),
),
);
}
EdgeInsets _paddingForSize(ButtonSize size) {
switch (size) {
case ButtonSize.small:
return EdgeInsets.symmetric(horizontal: 12, vertical: 8);
case ButtonSize.medium:
return EdgeInsets.symmetric(horizontal: 16, vertical: 12);
case ButtonSize.large:
return EdgeInsets.symmetric(horizontal: 24, vertical: 16);
}
}
}
enum ButtonSize { small, medium, large }
Master Flutter UI development with this comprehensive skill reference.