Last active
February 13, 2025 15:26
-
-
Save tejaswini-dev-techie/e65add1d83cb5c3de8af054cb966fc81 to your computer and use it in GitHub Desktop.
A Flutter CustomPainter implementation for a step-based progress indicator. This widget dynamically renders a progress bar with circles representing steps. Completed steps are filled, and previous steps display a tick mark. The current step is numbered inside the circle. Fully customizable for different step counts and progress states.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'package:flutter/material.dart'; | |
| import 'dart:ui' as ui; | |
| void main() { | |
| runApp(const MyApp()); | |
| } | |
| class MyApp extends StatelessWidget { | |
| const MyApp({super.key}); | |
| @override | |
| Widget build(BuildContext context) { | |
| return MaterialApp( | |
| debugShowCheckedModeBanner: false, | |
| home: Scaffold( | |
| body: Center( | |
| child: CustomPaint( | |
| size: const Size(328, 50), // Increased height for better visibility | |
| painter: StepperCustomPainter(filledRounds: 3, totalSteps: 7), | |
| ), | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| class StepperCustomPainter extends CustomPainter { | |
| final int filledRounds; | |
| final int totalSteps; | |
| StepperCustomPainter({required this.filledRounds, required this.totalSteps}); | |
| @override | |
| void paint(Canvas canvas, Size size) { | |
| // Background progress bar | |
| Paint backgroundPaint = Paint()..style = PaintingStyle.fill; | |
| backgroundPaint.color = const Color(0xffE9E5EF); | |
| canvas.drawRRect( | |
| RRect.fromRectAndCorners( | |
| Rect.fromLTWH(0, size.height * 0.4, size.width, size.height * 0.2), | |
| bottomRight: Radius.circular(size.width * 0.009), | |
| bottomLeft: Radius.circular(size.width * 0.009), | |
| topLeft: Radius.circular(size.width * 0.009), | |
| topRight: Radius.circular(size.width * 0.009), | |
| ), | |
| backgroundPaint, | |
| ); | |
| // Calculate step width dynamically | |
| double stepWidth = size.width / (totalSteps - 1); | |
| double circleRadius = size.width * 0.035; // Reduced circle size | |
| // Filled progress bar | |
| Paint filledPaint = Paint()..style = PaintingStyle.fill; | |
| filledPaint.color = const Color(0xff2E2E54); | |
| canvas.drawRRect( | |
| RRect.fromRectAndCorners( | |
| Rect.fromLTWH(0, size.height * 0.4, stepWidth * (filledRounds - 1), | |
| size.height * 0.2), | |
| bottomRight: Radius.circular(size.width * 0.009), | |
| bottomLeft: Radius.circular(size.width * 0.009), | |
| topLeft: Radius.circular(size.width * 0.009), | |
| topRight: Radius.circular(size.width * 0.009), | |
| ), | |
| filledPaint, | |
| ); | |
| // Circle positions | |
| for (int i = 0; i < totalSteps; i++) { | |
| double xPos = stepWidth * i; | |
| bool isFilled = i < filledRounds; | |
| bool hasTickMark = i < filledRounds - 1; | |
| Paint borderPaint = Paint() | |
| ..color = isFilled ? Colors.white : const Color(0xff2E2E54) | |
| ..style = PaintingStyle.stroke | |
| ..strokeWidth = size.width * 0.003; | |
| Paint fillPaint = Paint()..style = PaintingStyle.fill; | |
| fillPaint.color = | |
| isFilled ? const Color(0xff2E2E54) : const Color(0xffE9E5EF); | |
| // Draw circle with border | |
| canvas.drawCircle( | |
| Offset(xPos, size.height * 0.5), | |
| circleRadius, | |
| // size.width * 0.035, // Increased circle size - radius | |
| fillPaint, | |
| ); | |
| canvas.drawCircle( | |
| Offset(xPos, size.height * 0.5), | |
| circleRadius, | |
| // size.width * 0.035, // Increased circle size - radius | |
| borderPaint, | |
| ); | |
| // Draw tick mark for previous filled steps | |
| if (hasTickMark) { | |
| Path tickPath = Path(); | |
| double tickStartX = xPos - size.width * 0.012; | |
| double tickStartY = size.height * 0.52; | |
| double tickMidX = xPos - size.width * 0.004; | |
| double tickMidY = size.height * 0.58; | |
| double tickEndX = xPos + size.width * 0.012; | |
| double tickEndY = size.height * 0.42; | |
| tickPath.moveTo(tickStartX, tickStartY); | |
| tickPath.lineTo(tickMidX, tickMidY); | |
| tickPath.lineTo(tickEndX, tickEndY); | |
| Paint tickPaint = Paint() | |
| ..color = Colors.white | |
| ..style = PaintingStyle.stroke | |
| ..strokeWidth = size.width * 0.003 | |
| ..strokeCap = StrokeCap.round | |
| ..strokeJoin = StrokeJoin.round; | |
| canvas.drawPath(tickPath, tickPaint); | |
| } | |
| if (!hasTickMark) { | |
| // Draw step number inside the circle | |
| TextPainter textPainter = TextPainter( | |
| text: TextSpan( | |
| text: (i + 1).toString(), | |
| style: TextStyle( | |
| color: isFilled ? Colors.white : const Color(0xff2E2E54), | |
| fontSize: size.width * 0.03, | |
| fontWeight: FontWeight.bold, | |
| ), | |
| ), | |
| textDirection: TextDirection.ltr, | |
| ); | |
| textPainter.layout(); | |
| textPainter.paint( | |
| canvas, | |
| Offset( | |
| xPos - textPainter.width / 2, | |
| size.height * 0.5 - textPainter.height / 2, | |
| ), | |
| ); | |
| } | |
| } | |
| } | |
| @override | |
| bool shouldRepaint(covariant CustomPainter oldDelegate) { | |
| return true; | |
| } | |
| } |
Author
tejaswini-dev-techie
commented
Feb 13, 2025
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment