TL;DR: The blog introduces the Syncfusion Flutter Radial Gauge widget for visualizing data and tracking progress in circular formats. It covers creating animated circular progress indicators in styles like determinate, indeterminate, and segmented. Steps include configuring the Radial Gauge, adding dependencies, initializing the widget, and customizing progress bar styles. Detailed code examples show how to adjust axis labels, ticks, values, axis lines, range pointer styles, and add custom annotations.
The Syncfusion Flutter Radial Gauge widget is a multi-purpose data visualization widget. It visualizes different types of data and displays the progress of various processes in a circular format.
We have already published blogs on different use cases, such as creating a speedometer and temperature monitor using the Flutter Radial Gauge. To continue these use-case series posts, we will create different styles of animated circular progress indicators using the Syncfusion Flutter Radial Gauge.
The circular progress bar is used to visualize the work progress or an operation such as a download, file transfer, or installation. It can be used for showing different progress states, such as:
- Determinate
- Indeterminate
- Segmented progress
I’ve separated designing various styles for circular progress bars into a two-part blog. In this first part, you will learn about building different styles of a determinate-type circular progress bar.
Let’s get started!
Configuring the Radial Gauge widget
Creating a Flutter project
First, we need to configure the Radial Gauge widget in our application. To do this, follow the instructions in the Getting Started documentation to create a basic project in Flutter.
Add Radial Gauge dependency
Include the Syncfusion Flutter Radial Gauge package dependency in the pubspec.yaml file of your project.
syncfusion_flutter_gauges: ^18.2.44
Get packages
To download the package to the local disk, run the following command in your project terminal window.
$ flutter pub get |
Import package
Import the Radial Gauge package in the main.dart using the following code example.
import 'package:syncfusion_flutter_gauges/gauges.dart';
Add Radial Gauge widget
After importing the Radial Gauge package to the sample, initialize the Radial Gauge widget and add it to the widget tree, as shown in the following code example.
@override Widget build(BuildContext context) { return Scaffold( body: Center( child: SfRadialGauge(), ), ); }
Now that we have configured the Radial Gauge widget in our application. Let’s see the magical ways to create charming styles in it.
Various styles in determinate-type circular progress bar
A determinate-type progress bar is used when it is possible to estimate the completion percentage of a process. Let’s see how to design the following styles in a determinate progress bar by using the Flutter Radial Gauge:
- Normal progress bar
- Filled-track and filled-style progress bar
- Gradient progress bar with marker
- Semi-circular progress bar style
- Buffer progress bar
- Segmented circular progress bar
To achieve all these determinate-type circular progress bar designs, you need to use the radial axis, range pointer, and annotation features in the Radial Gauge.
Normal progress bar style
To get the normal progress bar style, we need to disable the labels and ticks properties in the RadialAxis class and set the axis range values in the minimum and maximum properties based on your design needs.
To show 100 percent progress, you need to define the axis’s minimum value as 0 and maximum as 100. You can also show the progress line (track) of the progress bar by customizing the axisLineStyle, as shown in the following code example.
SfRadialGauge(axes: <RadialAxis>[ RadialAxis( minimum: 0, maximum: 100, showLabels: false, showTicks: false, axisLineStyle: AxisLineStyle( thickness: 0.2, cornerStyle: CornerStyle.bothCurve, color: Color.fromARGB(30, 0, 169, 181), thicknessUnit: GaugeSizeUnit.factor, ), ) ]),
You can add a pointer to the progress bar by customizing the position and size of the RangePointer class. To show progress, the pointer Value should be updated with a delay action, such as a timer. Then, the pointer value will be dynamically updated by a timer for a specific duration of time. Refer to the following code example.
pointers: <GaugePointer>[ RangePointer( value: progressValue, cornerStyle: CornerStyle.bothCurve, width: 0.2, sizeUnit: GaugeSizeUnit.factor, ) ],
To add custom content to the center of the circular progress bar to indicate the completion of a progression or to convey its current status, you can use the annotations feature. To display the current updated progress value, set the pointer value to the annotation text.
annotations: <GaugeAnnotation>[ GaugeAnnotation( positionFactor: 0.1, angle: 90, widget: Text( progressValue.toStringAsFixed(0) + ' / 100', style: TextStyle(fontSize: 11), )) ])
Filled-track and filled-style progress bar
To fill the track color, set the axis line thickness to 1, thicknessUnit to GaugeSizeUnit.factor, and the axis line color to fill the entire gauge radius. Add the range pointer with the offset position to show the progression.
RadialAxis( minimum: 0, maximum: 100, showLabels: false, showTicks: false, startAngle: 270, endAngle: 270, axisLineStyle: AxisLineStyle( thickness: 1, color: const Color.fromARGB(255, 0, 169, 181), thicknessUnit: GaugeSizeUnit.factor, ), pointers: <GaugePointer>[ RangePointer( value: progressValue, width: 0.15 color: Colors.white, pointerOffset: 0.1, cornerStyle: CornerStyle.bothCurve, sizeUnit: GaugeSizeUnit.factor, ) ],
Add an annotation with the current progress value, as explained in the example of the previous code.
To fill the progress color, set the range pointer width to 0.95, sizeUnit to GaugeSizeUnit.factor, and the pointer’s color to fill the entire gauge radius. Add an axis line with appropriate thickness to show the track color.
RadialAxis( minimum: 0, maximum: 100, showLabels: false, showTicks: false, startAngle: 270, endAngle: 270, axisLineStyle: AxisLineStyle( thickness: 0.05, color: const Color.fromARGB(100, 0, 169, 181), thicknessUnit: GaugeSizeUnit.factor, ), pointers: <GaugePointer>[ RangePointer( value: progressValue, width: 0.95, pointerOffset: 0.05, sizeUnit: GaugeSizeUnit.factor, ) ], )
Gradient progress bar with marker style
To apply a gradient to the progress bar, set the SweepGradient with its appropriate colors and offset values to the gradient property of the range pointer. Also, add the MarkerPointer and the range pointer and use the same progress value to update both pointers, as shown in the following code example.
pointers: <GaugePointer>[ RangePointer( value: progressValue, width: 0.1, sizeUnit: GaugeSizeUnit.factor, cornerStyle: CornerStyle.startCurve, gradient: const SweepGradient(colors: <Color>[ Color(0xFF00a9b5), Color(0xFFa4edeb) ], stops: <double>[ 0.25, 0.75 ])), MarkerPointer( value: progressValue, markerType: MarkerType.circle, color: const Color(0xFF87e8e8), ) ],
Semi-circular progress bar style
You can customize the startAngle and endAngle properties of the radial axis to design full and semi-circular progress bars. To make a semi-circle progress bar, set the startAngle value to 180 and the endAngle value to 0, as shown in the following code example.
RadialAxis( showLabels: false, showTicks: false, startAngle: 180, endAngle: 0, radiusFactor: 0.7, canScaleToFit: true, axisLineStyle: AxisLineStyle( thickness: 0.1, color: const Color.fromARGB(30, 0, 169, 181), thicknessUnit: GaugeSizeUnit.factor, cornerStyle: CornerStyle.startCurve, ), pointers: <GaugePointer>[ RangePointer( value: progressValue, width: 0.1, sizeUnit: GaugeSizeUnit.factor, cornerStyle: CornerStyle.bothCurve) ],
Buffer-style progress bar
In this buffer-style progress bar, you can use a secondary progress indicator to denote the primary progression, which depends on the secondary progression. This style will allow you to visualize both primary and secondary progressions simultaneously. To add primary and secondary progress pointers, use two range pointers with different progress values.
pointers: <GaugePointer>[ RangePointer( value: secondaryProgressValue, width: 0.1, sizeUnit: GaugeSizeUnit.factor, color: const Color.fromARGB(120, 0, 169, 181), cornerStyle: CornerStyle.bothCurve), RangePointer( value: progressValue, width: 0.1, sizeUnit: GaugeSizeUnit.factor, cornerStyle: CornerStyle.bothCurve) ],
Segmented circular progress bar style
The segmented circular progress bar style allows you to divide a progress bar into multiple segments to visualize the progress of multi-sequence tasks.
Design the segmented progress bar by customizing the RadialAxis and the RangePointer. In addition, you need to add one more RadialAxis over the first axis to create the segmented line in the progress bar. The segmented lines are generated by enabling the major ticks for the secondary radial axis with a certain interval and disabling the other axis elements.
axes: <RadialAxis>[ // Create a primary radial axis RadialAxis( minimum: 0, maximum: 100, showLabels: false, showTicks: false, startAngle: 270, endAngle: 270, radiusFactor: 0.7, axisLineStyle: AxisLineStyle( thickness: 0.2, color: const Color.fromARGB(30, 0, 169, 181), thicknessUnit: GaugeSizeUnit.factor, ), pointers: <GaugePointer>[ RangePointer( value: progressValue, width: 0.05, pointerOffset: 0.07, sizeUnit: GaugeSizeUnit.factor, ) ], ), // Create a secondary radial axis for segmented line RadialAxis( minimum: 0, interval: 1, maximum: 4, showLabels: false, showTicks: true, showAxisLine: false, tickOffset: -0.05, offsetUnit: GaugeSizeUnit.factor, minorTicksPerInterval: 0, startAngle: 270, endAngle: 270, radiusFactor: 0.7, majorTickStyle: MajorTickStyle( length: 0.3, thickness: 3, lengthUnit: GaugeSizeUnit.factor, color: Colors.white), ) ]
Animate progression with real-time data
We have explored different styles of the circular progress bar. Now, let’s see how to update real-time data.
In the real-time application, the progress value will be fetched from a service and updated in the pointer. This demo uses a timer to stimulate progress updates that last 100 milliseconds. The app state will be changed to rebuild the widgets.
The progressValue variable sets the pointer value and the annotation’s text value. In each timer tick, the progressValue variable is incremented by 1 in the setState callback, as shown in the following code. This is to update the pointer and annotation text values.
_timer = Timer.periodic(const Duration(milliseconds: 100),(_timer) { setState(() { _progressValue++; }); });
In the RangePoiner, set the animationType property to linear and the timer duration to the pointer’s animationDuration property to make the animated progression.
pointers: <GaugePointer>[ RangePointer( value: _value1, width: 0.05, sizeUnit: GaugeSizeUnit.factor, enableAnimation: true, animationDuration: 100, animationType: AnimationType.linear) ],
GitHub reference:
You can download the sample code of all the explained circular progress bar types from this GitHub location. Supercharge your cross-platform apps with Syncfusion's robust .NET MAUI controls.
Conclusion
I hope you have enjoyed reading this blog and have a clear idea about how to create various styles for a determinate-type circular progress bar. In the upcoming part 2 blog, you can expect some more beautiful styles.
The Syncfusion Flutter Radial Gauge widget has been designed with flexible UI customization options to adapt to your app easily. It also includes developer-friendly APIs to increase your productivity.
The complete user guide is here, and you can also check out our other samples in this GitHub location. Additionally, you can download and check out our demo app in Google Play, the App Store, and our website.
The Radial Gauge is also available for our Xamarin, UWP, WinForms, WPF, Blazor, ASP .NET (Core, MVC, Web Forms), JavaScript, Angular, React, and Vue platforms.
Check them out and create stunning circular progress bars!
The newest version of Essential Studio®® is available on the license and downloads page for existing Syncfusion customers. If you are not a customer, try our 30-day free trial to test the latest features.
You can always contact us through our support forums, support portal, or feedback portal. We are always happy to assist you!
Comments (6)
many thanks for such useful article, but how to control widget size, i follow the tutorial but the widget is very large and is occupying the whole screen
Hi SHIMAA,
You can apply the size of the radial gauge widget by wrapping the widget inside the Container or SizedBox widgets and setting the appropriate size as shown in the code below.
Container(
height: 120,
width: 120,
child: SfRadialGauge()
)
Please refer to the below example code.
https://github.com/SyncfusionExamples/Flutter_circular_progress_bar/blob/master/circular_progress_bar/lib/main.dart#L81
Hello Sheik,
Thanks for such a great work.
I would like to ask for a little assistance. I tried using the circular_progress_bar inside an alert dialog but when I do, the progress doesn’t work, I don’t know what I wasn’t doing right.
Below is my code:
import ‘dart:async’;
import ‘package:flutter/material.dart’;
import ‘package:syncfusion_flutter_gauges/gauges.dart’;
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: ‘Flutter Demo’,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: ‘Flutter Demo Home Page’),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
// int _counter = 0;
double progressValue = 0;
double _size = 150;
Timer _timer;
@override
void initState() {
super.initState();
if (mounted) {
_timer = Timer.periodic(const Duration(milliseconds: 30), (Timer _timer) {
setState(() {
if (progressValue == 100) {
progressValue = 0;
} else {
progressValue++;
}
});
});
}
}
@override
void dispose() {
_timer.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: RaisedButton(
child: Text(‘Show alert’),
onPressed: () {
_showMyDialog();
},
),
),
);
}
Future _showMyDialog() {
return showDialog(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Colors.transparent,
content: Container(
height: _size,
width: _size,
decoration: BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.circular(10.0)
),
child: SfRadialGauge(axes: [
RadialAxis(
minimum: 0,
maximum: 100,
showLabels: false,
showTicks: false,
startAngle: 120,
endAngle: 120,
radiusFactor: 0.8,
axisLineStyle: AxisLineStyle(
thickness: 1,
color: Colors.transparent,
thicknessUnit: GaugeSizeUnit.factor,
),
pointers: [
RangePointer(
value: progressValue,
width: 0.1,
enableAnimation: true,
animationDuration: 20,
color: Colors.deepOrangeAccent,
pointerOffset: 0.1,
cornerStyle: CornerStyle.bothCurve,
animationType: AnimationType.linear,
sizeUnit: GaugeSizeUnit.factor,
)
],
annotations: [
GaugeAnnotation(
positionFactor: 0.5,
widget: Icon(
Icons.ac_unit,
color: Colors.deepOrangeAccent,
size: 34.0,
),
),
]
)
]
),
),
);
},
);
}
}
Hi DAVID,
The reported problem occurs due to the showDialog widget of Flutter. The progress value incremented in the periodic timer is not bound to the range pointer value once the radial gauge is loaded using showDialog. We have checked by binding the progress value as the text of the Text widget and in that widget also, the incremented progress value is not getting updated. As this is a flutter framework (showDialog) issue, we will log this in GitHub.
As of now, we have modified the provided code by returning the radial gauge as the child of alert dialog from a separate stateful widget. Please find the modified sample from the below link
https://www.syncfusion.com/downloads/support/directtrac/318568/ze/progess_bar_2-346038809
where is the second part?
Hi EMILIANO,
Please find the Part-2 link from below,
https://www.syncfusion.com/blogs/post/create-stunning-circular-progress-bars-with-flutter-radial-gauge-part-2.aspx
Regards,
Gayathri R
Comments are closed.