PrimitiveCircularProgress
A circular progress indicator that supports both indeterminate (spinning) and determinate (value-based) modes. This component demonstrates how to combine CustomPaint with AnimationController for smooth animations.
Overview
PrimitiveCircularProgress renders a circular progress bar using CustomPaint. It can either spin indefinitely to indicate loading or show a specific percentage of completion.
Primitives Used: CustomPaint, Canvas, AnimationController
Primitive Circular Progress ShowcaseOpen in new tab ↗
Basic Usage
import 'package:primitive_ui/primitive_ui.dart';
// Indeterminate (spinning)
PrimitiveCircularProgress()
// Determinate (50%)
PrimitiveCircularProgress(
value: 0.5,
)API Reference
Constructor
PrimitiveCircularProgress({
Key? key,
double? value,
Color color = const Color(0xFF2196F3),
double strokeWidth = 4.0,
double size = 40.0,
String? semanticsLabel,
})Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
value | double? | ✗ | null | The value of the progress (0.0 to 1.0). If null, the indicator is indeterminate. |
color | Color | ✗ | Color(0xFF2196F3) | The color of the progress indicator. |
strokeWidth | double | ✗ | 4.0 | The width of the line used to draw the circle. |
size | double | ✗ | 40.0 | The diameter of the progress indicator. |
semanticsLabel | String? | ✗ | null | Accessibility label (defaults to "Loading" or "Progress") |
Examples
Modes
Indeterminate
PrimitiveCircularProgress(
color: Colors.blue,
)Custom Styling
PrimitiveCircularProgress(
value: 0.3,
color: Colors.orange,
size: 60.0,
strokeWidth: 8.0,
)Loading State Example
class LoadingButton extends StatelessWidget {
final bool isLoading;
final VoidCallback onTap;
LoadingButton({required this.isLoading, required this.onTap});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: isLoading ? null : onTap,
child: PrimitiveCard(
color: Colors.blue,
child: Center(
child: isLoading
? PrimitiveCircularProgress(
color: Colors.white,
size: 24,
strokeWidth: 3,
)
: Text('Submit', style: TextStyle(color: Colors.white)),
),
),
);
}
}Implementation Details
The PrimitiveCircularProgress is implemented using:
AnimationController: Used primarily for the indeterminate state to rotate the visual.AnimatedBuilder: Listens to the controller and triggers repaints.CustomPaint:- Determinate: Draws a partial arc based on the
valueproperty (e.g.,2 * pi * value). - Indeterminate: Rotates a fixed-length arc based on the animation value.
- Determinate: Draws a partial arc based on the
Drawing Logic
if (value != null) {
// Determinate: draw arc based on value
double sweepAngle = 2 * pi * value!.clamp(0.0, 1.0);
canvas.drawArc(rect, -pi / 2, sweepAngle, false, paint);
} else {
// Indeterminate: rotate fixed arc
// ... rotation logic ...
canvas.drawArc(rect, startAngle, sweepAngle, false, paint);
}Limitations
Known Limitations:
- The indeterminate animation is a simple rotation; it does not currently implement the complex “worm” expansion/contraction effect of the Material Design spinner to keep the primitive implementation simple.
Related Components
- PrimitiveSlider - Input for values
- PrimitiveCard - Container often used with loaders
Source Code
View the complete implementation in the GitHub repository .
Last updated on