Skip to main content

LinearProgressIndicatorAppi

A customizable linear progress indicator widget that displays progress with optional icons and precise positioning control for enhanced user experience.

Features

  • Progress Tracking: Visual representation of task completion
  • Icon Support: Optional leading icon for context
  • Precise Positioning: Control current and starting positions
  • Custom Dimensions: Configurable width and height
  • Smooth Animation: Built-in progress animations
  • Flexible Styling: Customizable appearance and colors
  • Responsive Design: Adapts to different screen sizes

Usage

Basic Progress Indicator

LinearProgressIndicatorAppi(
len: 100,
current_position: 45,
)

Progress with Icon

LinearProgressIndicatorAppi(
len: 100,
current_position: 75,
icon: Icons.download,
width: 300,
height: 8,
)

Custom Starting Position

LinearProgressIndicatorAppi(
len: 100,
current_position: 60,
from_position: 20, // Start progress from 20%
icon: Icons.upload,
width: 250,
height: 6,
)

Parameters

| Name | Type | Description | Default | |------|------|-------------|---------|| | len | double | Required. Total length/maximum value of the progress | - | | current_position | double | Required. Current progress position | - | | from_position | double? | Starting position for progress calculation | 0.0 | | icon | IconData? | Optional icon to display alongside the progress bar | null | | width | double? | Width of the progress indicator | null (uses available width) | | height | double? | Height of the progress indicator | 4.0 |

Behavior

Progress Calculation

  • Progress is calculated as: (current_position - from_position) / (len - from_position)
  • Values are automatically clamped between 0.0 and 1.0
  • Supports both forward and backward progress tracking

Visual Elements

  • Progress Bar: Shows completion percentage with smooth animations
  • Icon: Optional leading icon for visual context
  • Container: Responsive layout that adapts to available space

Animation

  • Smooth transitions when progress values change
  • Built-in Material Design animations
  • Configurable animation duration and curves

Best Practices

  1. Meaningful Values: Use meaningful ranges for len and positions
  2. Consistent Updates: Update progress values regularly for smooth animation
  3. Icon Context: Choose icons that relate to the task being tracked
  4. Accessibility: Ensure sufficient contrast for progress visibility
  5. Responsive Design: Test on different screen sizes
  6. Performance: Avoid excessive progress updates that could impact performance

Examples

File Download Progress

class DownloadProgress extends StatefulWidget {

_DownloadProgressState createState() => _DownloadProgressState();
}

class _DownloadProgressState extends State<DownloadProgress> {
double downloadProgress = 0.0;
double totalSize = 100.0;

void _simulateDownload() {
Timer.periodic(Duration(milliseconds: 100), (timer) {
setState(() {
downloadProgress += 2.0;
if (downloadProgress >= totalSize) {
downloadProgress = totalSize;
timer.cancel();
}
});
});
}


Widget build(BuildContext context) {
return Column(
children: [
Text('Downloading... ${(downloadProgress / totalSize * 100).toInt()}%'),
SizedBox(height: 16),
LinearProgressIndicatorAppi(
len: totalSize,
current_position: downloadProgress,
icon: Icons.download,
width: 300,
height: 8,
),
SizedBox(height: 16),
ElevatedButton(
onPressed: downloadProgress >= totalSize ? null : _simulateDownload,
child: Text('Start Download'),
),
],
);
}
}

Task Completion Tracker

class TaskTracker extends StatefulWidget {
final List<Task> tasks;

TaskTracker({required this.tasks});


_TaskTrackerState createState() => _TaskTrackerState();
}

class _TaskTrackerState extends State<TaskTracker> {

Widget build(BuildContext context) {
final completedTasks = widget.tasks.where((task) => task.isCompleted).length;
final totalTasks = widget.tasks.length;

return Card(
margin: EdgeInsets.all(16),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Project Progress',
style: Theme.of(context).textTheme.headlineSmall,
),
SizedBox(height: 8),
Text('$completedTasks of $totalTasks tasks completed'),
SizedBox(height: 16),
LinearProgressIndicatorAppi(
len: totalTasks.toDouble(),
current_position: completedTasks.toDouble(),
icon: Icons.task_alt,
width: double.infinity,
height: 10,
),
SizedBox(height: 16),
Text(
'${(completedTasks / totalTasks * 100).toInt()}% Complete',
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
],
),
),
);
}
}

Multi-Stage Process

class MultiStageProgress extends StatefulWidget {

_MultiStageProgressState createState() => _MultiStageProgressState();
}

class _MultiStageProgressState extends State<MultiStageProgress> {
int currentStage = 1;
final int totalStages = 5;
final List<String> stageNames = [
'Initialization',
'Data Processing',
'Analysis',
'Validation',
'Completion'
];

void _nextStage() {
if (currentStage < totalStages) {
setState(() => currentStage++);
}
}

void _previousStage() {
if (currentStage > 1) {
setState(() => currentStage--);
}
}


Widget build(BuildContext context) {
return Column(
children: [
Text(
'Stage ${currentStage}: ${stageNames[currentStage - 1]}',
style: Theme.of(context).textTheme.headlineSmall,
),
SizedBox(height: 16),
LinearProgressIndicatorAppi(
len: totalStages.toDouble(),
current_position: currentStage.toDouble(),
icon: Icons.timeline,
width: 350,
height: 12,
),
SizedBox(height: 24),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: currentStage > 1 ? _previousStage : null,
child: Text('Previous'),
),
ElevatedButton(
onPressed: currentStage < totalStages ? _nextStage : null,
child: Text('Next'),
),
],
),
],
);
}
}

Upload Progress with Resume

class UploadProgress extends StatefulWidget {

_UploadProgressState createState() => _UploadProgressState();
}

class _UploadProgressState extends State<UploadProgress> {
double uploadedBytes = 0.0;
double totalBytes = 1000.0;
double resumeFromBytes = 0.0;
bool isUploading = false;
Timer? uploadTimer;

void _startUpload() {
setState(() => isUploading = true);
uploadTimer = Timer.periodic(Duration(milliseconds: 50), (timer) {
setState(() {
uploadedBytes += 5.0;
if (uploadedBytes >= totalBytes) {
uploadedBytes = totalBytes;
isUploading = false;
timer.cancel();
}
});
});
}

void _pauseUpload() {
uploadTimer?.cancel();
setState(() {
isUploading = false;
resumeFromBytes = uploadedBytes;
});
}

void _resetUpload() {
uploadTimer?.cancel();
setState(() {
uploadedBytes = 0.0;
resumeFromBytes = 0.0;
isUploading = false;
});
}


Widget build(BuildContext context) {
final progressPercent = (uploadedBytes / totalBytes * 100).toInt();

return Column(
children: [
Text('Upload Progress: $progressPercent%'),
if (resumeFromBytes > 0)
Text('Resumed from: ${(resumeFromBytes / totalBytes * 100).toInt()}%'),
SizedBox(height: 16),
LinearProgressIndicatorAppi(
len: totalBytes,
current_position: uploadedBytes,
from_position: resumeFromBytes,
icon: Icons.cloud_upload,
width: 400,
height: 8,
),
SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: !isUploading && uploadedBytes < totalBytes
? _startUpload
: null,
child: Text('Start'),
),
ElevatedButton(
onPressed: isUploading ? _pauseUpload : null,
child: Text('Pause'),
),
ElevatedButton(
onPressed: _resetUpload,
child: Text('Reset'),
),
],
),
],
);
}


void dispose() {
uploadTimer?.cancel();
super.dispose();
}
}

Reading Progress Indicator

class ReadingProgress extends StatefulWidget {
final String content;

ReadingProgress({required this.content});


_ReadingProgressState createState() => _ReadingProgressState();
}

class _ReadingProgressState extends State<ReadingProgress> {
final ScrollController _scrollController = ScrollController();
double readingProgress = 0.0;


void initState() {
super.initState();
_scrollController.addListener(_updateProgress);
}

void _updateProgress() {
if (_scrollController.hasClients) {
final maxScroll = _scrollController.position.maxScrollExtent;
final currentScroll = _scrollController.position.pixels;
setState(() {
readingProgress = maxScroll > 0 ? (currentScroll / maxScroll * 100) : 0;
});
}
}


Widget build(BuildContext context) {
return Column(
children: [
Container(
padding: EdgeInsets.all(16),
child: Column(
children: [
Text('Reading Progress'),
SizedBox(height: 8),
LinearProgressIndicatorAppi(
len: 100,
current_position: readingProgress,
icon: Icons.menu_book,
width: double.infinity,
height: 6,
),
Text('${readingProgress.toInt()}% completed'),
],
),
),
Expanded(
child: SingleChildScrollView(
controller: _scrollController,
padding: EdgeInsets.all(16),
child: Text(
widget.content,
style: TextStyle(fontSize: 16, height: 1.5),
),
),
),
],
);
}


void dispose() {
_scrollController.dispose();
super.dispose();
}
}

See Also