flutter_dotted 1.0.0
flutter_dotted: ^1.0.0 copied to clipboard
A lightweight, highly customizable Flutter package to draw dotted and dashed borders, rounded borders, circles, lines, and marching ants around containers.
import 'package:flutter/material.dart';
import 'package:flutter_dotted/flutter_dotted.dart';
void main() {
runApp(const FlutterDottedExampleApp());
}
class FlutterDottedExampleApp extends StatelessWidget {
const FlutterDottedExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Dotted Showcase',
debugShowCheckedModeBanner: false,
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF6750A4),
brightness: Brightness.light,
),
),
darkTheme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFFD0BCFF),
brightness: Brightness.dark,
),
),
themeMode: ThemeMode.system,
home: const ShowcaseHomeScreen(),
);
}
}
class ShowcaseHomeScreen extends StatefulWidget {
const ShowcaseHomeScreen({super.key});
@override
State<ShowcaseHomeScreen> createState() => _ShowcaseHomeScreenState();
}
class _ShowcaseHomeScreenState extends State<ShowcaseHomeScreen> {
// Playground state
double _strokeWidth = 2.0;
double _dash = 6.0;
double _gap = 4.0;
double _radius = 16.0;
bool _isRoundCap = true;
bool _isAnimated = false;
bool _useGradient = false;
Color _selectedColor = const Color(0xFF6750A4);
final List<Color> _colorPalette = const [
Color(0xFF6750A4),
Color(0xFF00668B),
Color(0xFF006D44),
Color(0xFFBA1A1A),
Color(0xFF7D5260),
Color(0xFFE65100),
];
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
title: const Text('Flutter Dotted'),
centerTitle: true,
elevation: 0,
),
body: ListView(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
children: [
// Section 1: Interactive Playground
_buildSectionHeader('Interactive Playground', Icons.tune),
const SizedBox(height: 8),
_buildPlaygroundCard(theme),
const SizedBox(height: 24),
// Section 2: Shapes & Radius
_buildSectionHeader('Rounded Borders & Shapes', Icons.category),
const SizedBox(height: 8),
_buildShapesGrid(theme),
const SizedBox(height: 24),
// Section 3: Animated Upload Dropzone
_buildSectionHeader('Animated "Marching Ants"', Icons.animation),
const SizedBox(height: 8),
_buildAnimatedUploadCard(theme),
const SizedBox(height: 24),
// Section 4: Ticket / Voucher with Dotted Divider
_buildSectionHeader('Voucher with Dotted Dividers', Icons.confirmation_number_outlined),
const SizedBox(height: 8),
_buildVoucherCard(theme),
const SizedBox(height: 24),
// Section 5: Circular Badges & Avatars
_buildSectionHeader('Circular Dotted Rings', Icons.account_circle_outlined),
const SizedBox(height: 8),
_buildCircularBadgesRow(theme),
const SizedBox(height: 32),
],
),
);
}
Widget _buildSectionHeader(String title, IconData icon) {
return Row(
children: [
Icon(icon, size: 20, color: Theme.of(context).colorScheme.primary),
const SizedBox(width: 8),
Text(
title,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
],
);
}
Widget _buildPlaygroundCard(ThemeData theme) {
return Card(
elevation: 0,
color: theme.colorScheme.surfaceContainerHighest.withValues(alpha: 0.5),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
// Live Preview
Container(
height: 140,
width: double.infinity,
alignment: Alignment.center,
child: FlutterDotted(
strokeWidth: _strokeWidth,
dash: _dash,
gap: _gap,
strokeCap: _isRoundCap ? StrokeCap.round : StrokeCap.butt,
borderRadius: BorderRadius.circular(_radius),
color: _selectedColor,
gradient: _useGradient
? LinearGradient(
colors: [_selectedColor, Colors.amber.shade700],
)
: null,
backgroundColor: theme.colorScheme.surface,
animate: _isAnimated,
animationDuration: const Duration(seconds: 1),
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
_isAnimated ? Icons.auto_awesome : Icons.dashboard_outlined,
color: _selectedColor,
size: 28,
),
const SizedBox(height: 6),
Text(
'Live Preview Box',
style: theme.textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.w600,
),
),
],
),
),
),
const Divider(height: 28),
// Controls
_buildSlider(
label: 'Stroke Width',
value: _strokeWidth,
min: 1.0,
max: 10.0,
onChanged: (v) => setState(() => _strokeWidth = v),
),
_buildSlider(
label: 'Dash Length',
value: _dash,
min: 1.0,
max: 20.0,
onChanged: (v) => setState(() => _dash = v),
),
_buildSlider(
label: 'Gap Spacing',
value: _gap,
min: 1.0,
max: 20.0,
onChanged: (v) => setState(() => _gap = v),
),
_buildSlider(
label: 'Corner Radius',
value: _radius,
min: 0.0,
max: 40.0,
onChanged: (v) => setState(() => _radius = v),
),
const SizedBox(height: 8),
// Toggles
Wrap(
spacing: 8,
runSpacing: 8,
alignment: WrapAlignment.center,
children: [
FilterChip(
label: Text(_isRoundCap ? 'Cap: Round' : 'Cap: Butt'),
selected: _isRoundCap,
onSelected: (val) => setState(() => _isRoundCap = val),
),
FilterChip(
label: const Text('Animate'),
selected: _isAnimated,
onSelected: (val) => setState(() => _isAnimated = val),
),
FilterChip(
label: const Text('Gradient'),
selected: _useGradient,
onSelected: (val) => setState(() => _useGradient = val),
),
],
),
const SizedBox(height: 12),
// Color Selector
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: _colorPalette.map((color) {
final isSelected = _selectedColor == color;
return GestureDetector(
onTap: () => setState(() => _selectedColor = color),
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 5),
width: 28,
height: 28,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
border: isSelected
? Border.all(color: theme.colorScheme.onSurface, width: 2)
: null,
),
),
);
}).toList(),
),
],
),
),
);
}
Widget _buildSlider({
required String label,
required double value,
required double min,
required double max,
required ValueChanged<double> onChanged,
}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 2.0),
child: Row(
children: [
SizedBox(
width: 96,
child: Text(
'$label: ${value.toStringAsFixed(1)}',
style: Theme.of(context).textTheme.bodySmall,
),
),
Expanded(
child: Slider(
value: value,
min: min,
max: max,
onChanged: onChanged,
),
),
],
),
);
}
Widget _buildShapesGrid(ThemeData theme) {
return Row(
children: [
// Sharp Rect
Expanded(
child: FlutterDotted(
strokeWidth: 2,
dash: 5,
gap: 4,
shape: DottedShape.rect,
color: theme.colorScheme.primary,
backgroundColor: theme.colorScheme.primaryContainer.withValues(alpha: 0.3),
padding: const EdgeInsets.all(16),
child: const Center(
child: Text('Sharp Rect', style: TextStyle(fontWeight: FontWeight.w600)),
),
),
),
const SizedBox(width: 12),
// Rounded Rect
Expanded(
child: FlutterDotted(
strokeWidth: 2,
dash: 5,
gap: 4,
borderRadius: BorderRadius.circular(16),
color: theme.colorScheme.secondary,
backgroundColor: theme.colorScheme.secondaryContainer.withValues(alpha: 0.3),
padding: const EdgeInsets.all(16),
child: const Center(
child: Text('Rounded Rect', style: TextStyle(fontWeight: FontWeight.w600)),
),
),
),
],
);
}
Widget _buildAnimatedUploadCard(ThemeData theme) {
return FlutterDotted.animated(
strokeWidth: 2.5,
dash: 8,
gap: 6,
strokeCap: StrokeCap.round,
borderRadius: BorderRadius.circular(16),
color: theme.colorScheme.primary,
backgroundColor: theme.colorScheme.primaryContainer.withValues(alpha: 0.2),
padding: const EdgeInsets.symmetric(vertical: 24, horizontal: 16),
animationDuration: const Duration(milliseconds: 1200),
child: Center(
child: Column(
children: [
Icon(Icons.cloud_upload_outlined, size: 40, color: theme.colorScheme.primary),
const SizedBox(height: 8),
Text(
'Drag & drop files here to upload',
style: theme.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: 4),
Text(
'Supports PNG, JPG, PDF up to 25MB',
style: theme.textTheme.bodySmall?.copyWith(color: theme.colorScheme.outline),
),
],
),
),
);
}
Widget _buildVoucherCard(ThemeData theme) {
return FlutterDotted(
strokeWidth: 1.5,
dash: 6,
gap: 4,
borderRadius: BorderRadius.circular(16),
color: Colors.amber.shade800,
backgroundColor: Colors.amber.shade50.withValues(alpha: 0.5),
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'50% OFF DISCOUNT',
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
color: Colors.amber.shade900,
),
),
Text(
'Valid until Dec 31, 2026',
style: theme.textTheme.bodySmall?.copyWith(color: Colors.black54),
),
],
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: Colors.amber.shade800,
borderRadius: BorderRadius.circular(8),
),
child: const Text(
'PROMO',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
),
),
],
),
),
// Horizontal Dotted Divider
FlutterDottedLine(
direction: Axis.horizontal,
strokeWidth: 1.5,
dash: 5,
gap: 4,
color: Colors.amber.shade800,
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'CODE: DOTTED2026',
style: TextStyle(letterSpacing: 1.2, fontWeight: FontWeight.bold),
),
TextButton(
onPressed: () {},
child: const Text('Redeem'),
),
],
),
),
],
),
);
}
Widget _buildCircularBadgesRow(ThemeData theme) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// Circular Avatar Border
FlutterDotted(
shape: DottedShape.circle,
strokeWidth: 3,
dash: 5,
gap: 3,
color: Colors.teal,
padding: const EdgeInsets.all(4),
child: const CircleAvatar(
radius: 32,
backgroundColor: Colors.teal,
child: Icon(Icons.person, color: Colors.white, size: 36),
),
),
// Animated Circular Badge
FlutterDotted.animated(
shape: DottedShape.circle,
strokeWidth: 2.5,
dash: 6,
gap: 4,
color: Colors.deepOrange,
animationDuration: const Duration(seconds: 2),
padding: const EdgeInsets.all(8),
child: const Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.bolt, color: Colors.deepOrange, size: 28),
Text('HOT', style: TextStyle(fontSize: 10, fontWeight: FontWeight.bold)),
],
),
),
// Gradient Circle
FlutterDotted(
shape: DottedShape.circle,
strokeWidth: 3,
dash: 4,
gap: 4,
gradient: const SweepGradient(
colors: [Colors.purple, Colors.pink, Colors.blue, Colors.purple],
),
padding: const EdgeInsets.all(8),
child: const Icon(Icons.star, color: Colors.purple, size: 32),
),
],
);
}
}