new_loading_indicator 4.0.0
new_loading_indicator: ^4.0.0 copied to clipboard
A fork of the loading_indicator package, providing a rich collection of loading animations for Flutter applications.
import 'package:material_ui/material_ui.dart';
import 'package:new_loading_indicator/new_loading_indicator.dart';
void main() {
runApp(const App());
}
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Loading Indicators Demo',
theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.blue),
home: const LoadingGallery(),
);
}
}
class LoadingGallery extends StatefulWidget {
const LoadingGallery({super.key});
@override
State<LoadingGallery> createState() => _LoadingGalleryState();
}
class _LoadingGalleryState extends State<LoadingGallery> {
/// The largest stroke width the slider offers. Its lowest position means
/// "not set", so every indicator uses its default.
static const double _maxStrokeWidth = 8;
Indicator _selectedIndicator = Indicator.ballPulse;
final List<Color> _colors = [Colors.blue];
/// Drives the preview. It stays the same as the selection changes, so a
/// paused preview stays paused when another indicator is picked.
final LoadingIndicatorController _controller = LoadingIndicatorController();
double _target = 0.5;
double? _strokeWidth;
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _togglePlayback() {
switch (_controller.status) {
case .playing || .pausing:
_controller.pause();
case .paused || .detached:
_controller.resume();
}
}
Future<void> _pauseAtTarget(LoadingIndicatorPauseBehavior behavior) async {
try {
await _controller.pauseAt(_target, behavior: behavior);
} on LoadingIndicatorCommandCanceled {
// A newer command replaced this one before the target was reached.
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Loading Indicators')),
body: LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth > 600.0) {
return _buildWideLayout();
}
return _buildNarrowLayout();
},
),
);
}
Widget _buildWideLayout() {
return Row(
children: [
Expanded(flex: 2, child: _buildIndicatorList()),
const VerticalDivider(),
Expanded(flex: 3, child: _buildPreviewArea()),
],
);
}
Widget _buildNarrowLayout() {
return Column(
children: [
Expanded(flex: 3, child: _buildPreviewArea()),
const Divider(),
Expanded(flex: 2, child: _buildIndicatorList()),
],
);
}
Widget _buildPreviewArea() {
final TextTheme textTheme = Theme.of(context).textTheme;
return Center(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 360.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 150.0,
height: 150.0,
child: LoadingIndicator(
indicatorType: _selectedIndicator,
colors: _colors,
strokeWidth: _strokeWidth,
controller: _controller,
),
),
const SizedBox(height: 24.0),
Text(_selectedIndicator.name, style: textTheme.titleLarge),
const SizedBox(height: 8.0),
ListenableBuilder(
listenable: _controller,
builder: (context, _) => Text(
'${_controller.status.name} · '
'${_controller.progress?.toStringAsFixed(2) ?? '–'}',
key: const Key('playback-status'),
style: textTheme.bodyMedium,
),
),
const SizedBox(height: 16.0),
_buildPlaybackControls(),
const SizedBox(height: 16.0),
_buildSliders(textTheme),
],
),
),
),
);
}
Widget _buildPlaybackControls() {
final String target = _target.toStringAsFixed(2);
return Wrap(
alignment: WrapAlignment.center,
crossAxisAlignment: WrapCrossAlignment.center,
spacing: 8.0,
runSpacing: 8.0,
children: [
ListenableBuilder(
listenable: _controller,
builder: (context, _) {
final bool paused = switch (_controller.status) {
.paused || .detached => true,
.playing || .pausing => false,
};
return IconButton.filled(
tooltip: paused ? 'Resume' : 'Pause',
onPressed: _togglePlayback,
icon: Icon(paused ? Icons.play_arrow : Icons.pause),
);
},
),
FilledButton.tonal(
onPressed: () => _pauseAtTarget(.waitUntilTarget),
child: Text('Play to $target'),
),
OutlinedButton(
onPressed: () => _pauseAtTarget(.jumpToTarget),
child: Text('Jump to $target'),
),
],
);
}
Widget _buildSliders(TextTheme textTheme) {
final double? strokeWidth = _strokeWidth;
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'Target ${_target.toStringAsFixed(2)}',
style: textTheme.labelLarge,
),
Slider(
key: const Key('target-slider'),
value: _target,
divisions: 20,
label: _target.toStringAsFixed(2),
onChanged: (value) => setState(() => _target = value),
),
Text(
strokeWidth == null
? 'Stroke width: default'
: 'Stroke width ${strokeWidth.toStringAsFixed(0)}',
style: textTheme.labelLarge,
),
Slider(
key: const Key('stroke-width-slider'),
value: strokeWidth ?? 0,
max: _maxStrokeWidth,
divisions: _maxStrokeWidth.toInt(),
label: strokeWidth?.toStringAsFixed(0) ?? 'default',
onChanged: (value) =>
setState(() => _strokeWidth = value == 0 ? null : value),
),
],
);
}
Widget _buildIndicatorList() {
return ListView.builder(
itemCount: Indicator.values.length,
itemBuilder: (context, index) {
final indicator = Indicator.values[index];
return ListTile(
leading: SizedBox(
width: 30.0,
height: 30.0,
child: LoadingIndicator(indicatorType: indicator, colors: _colors),
),
title: Text(indicator.name),
selected: _selectedIndicator == indicator,
onTap: () => setState(() => _selectedIndicator = indicator),
);
},
);
}
}