<h1 id="custom-widget-development-in-flutter">Custom Widget Development in Flutter</h1> <p>Creating custom widgets is a fundamental part of Flutter development. In this article, we'll explore how to create effective custom widgets that are reusable, maintainable, and performant.</p> <h2 id="basic-custom-widget-structure">1. Basic Custom Widget Structure</h2> <h3 id="stateless-custom-widget">Stateless Custom Widget</h3> <pre>class CustomCard extends StatelessWidget { final String title; final String description; final Color backgroundColor;
const CustomCard({ required this.title, required this.description, this.backgroundColor = Colors.white, });
@override Widget build(BuildContext context) { return Container( padding: EdgeInsets.all(16), decoration: BoxDecoration( color: backgroundColor, borderRadius: BorderRadius.circular(8), boxShadow: [ BoxShadow( color: Colors.black12, blurRadius: 4, offset: Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: Theme.of(context).textTheme.titleLarge, ), SizedBox(height: 8), Text( description, style: Theme.of(context).textTheme.bodyMedium, ), ], ), ); } } </pre> <h3 id="stateful-custom-widget">Stateful Custom Widget</h3> <pre>class AnimatedCustomButton extends StatefulWidget { final String text; final VoidCallback onPressed; final Color color;
const AnimatedCustomButton({ required this.text, required this.onPressed, this.color = Colors.blue, });
@override State<AnimatedCustomButton> createState() => _AnimatedCustomButtonState(); }
class _AnimatedCustomButtonState extends State<AnimatedCustomButton> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _scaleAnimation;
@override void initState() { super.initState(); _controller = AnimationController( duration: Duration(milliseconds: 200), vsync: this, ); _scaleAnimation = Tween<double>(begin: 1.0, end: 0.95).animate( CurvedAnimation(parent: _controller, curve: Curves.easeInOut), ); }
@override Widget build(BuildContext context) { return GestureDetector( onTapDown: (_) => controller.forward(), onTapUp: () { _controller.reverse(); widget.onPressed(); }, onTapCancel: () => _controller.reverse(), child: ScaleTransition( scale: _scaleAnimation, child: Container( padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12), decoration: BoxDecoration( color: widget.color, borderRadius: BorderRadius.circular(8), ), child: Text( widget.text, style: TextStyle(color: Colors.white), ), ), ), ); }
@override void dispose() { _controller.dispose(); super.dispose(); } } </pre> <h2 id="advanced-custom-widget-techniques">2. Advanced Custom Widget Techniques</h2> <h3 id="custom-paint-widget">Custom Paint Widget</h3> <pre>class CustomProgressIndicator extends StatelessWidget { final double progress; final Color color;
const CustomProgressIndicator({ required this.progress, this.color = Colors.blue, });
@override Widget build(BuildContext context) { return CustomPaint( painter: ProgressPainter(progress: progress, color: color), child: Container(), ); } }
class ProgressPainter extends CustomPainter { final double progress; final Color color;
ProgressPainter({required this.progress, required this.color});
@override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = color ..style = PaintingStyle.stroke ..strokeWidth = 4.0;
final center = Offset(size.width / 2, size.height / 2);
final radius = size.width / 2 - 2;
canvas.drawCircle(center, radius, paint);
final progressPaint = Paint()
..color = color
..style = PaintingStyle.stroke
..strokeWidth = 4.0
..strokeCap = StrokeCap.round;
canvas.drawArc(
Rect.fromCircle(center: center, radius: radius),
-pi / 2,
2 * pi * progress,
false,
progressPaint,
);
}
@override bool shouldRepaint(ProgressPainter oldDelegate) => oldDelegate.progress != progress || oldDelegate.color != color; } </pre> <h2 id="best-practices-for-custom-widgets">3. Best Practices for Custom Widgets</h2> <h3 id="proper-parameter-validation">1. Proper Parameter Validation</h3> <pre>class ValidatedCustomWidget extends StatelessWidget { final String title; final int count;
const ValidatedCustomWidget({ required this.title, required this.count, }) : assert(count >= 0, 'Count must be non-negative');
@override
Widget build(BuildContext context) {
return Container(
child: Text('$title: $count'),
);
}
}
</pre>
<h3 id="using-theme-data">2. Using Theme Data</h3>
<pre>class ThemedCustomWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Container(
decoration: BoxDecoration(
color: theme.colorScheme.primary,
borderRadius: BorderRadius.circular(theme.shape.borderRadius),
),
child: Text(
'Themed Widget',
style: theme.textTheme.titleLarge?.copyWith(
color: theme.colorScheme.onPrimary,
),
),
);
}
}
</pre>
<h3 id="implementing-proper-documentation">3. Implementing Proper Documentation</h3>
<pre>/// A custom widget that displays a progress indicator with customizable colors.
///
/// This widget can be used to show progress in various parts of the application.
/// It supports both determinate and indeterminate progress states.
///
/// Example:
/// dart /// CustomProgressIndicator( /// progress: 0.75, /// color: Colors.blue, /// ) ///
class CustomProgressIndicator extends StatelessWidget {
// ... implementation
}
</pre>
<h2 id="testing-custom-widgets">4. Testing Custom Widgets</h2>
<h3 id="unit-testing">Unit Testing</h3>
<pre>void main() {
testWidgets('CustomCard displays correct content', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: CustomCard(
title: 'Test Title',
description: 'Test Description',
),
),
);
expect(find.text(&#39;Test Title&#39;), findsOneWidget);
expect(find.text(&#39;Test Description&#39;), findsOneWidget);
}); } </pre> <h2 id="performance-considerations">5. Performance Considerations</h2> <ol> <li><strong>Use const constructors</strong> where possible</li> <li><strong>Implement shouldRepaint</strong> properly in CustomPainter</li> <li><strong>Use RepaintBoundary</strong> for complex widgets</li> <li><strong>Minimize rebuilds</strong> by extracting sub-widgets</li> <li><strong>Use keys</strong> appropriately for state management</li> </ol> <p>By following these guidelines and examples, you can create custom widgets that are:</p> <ul> <li>Reusable across your application</li> <li>Easy to maintain and modify</li> <li>Performant and efficient</li> <li>Well-documented and tested</li> <li>Consistent with your app's design system</li> </ul>