flutter_dotted 1.0.0 copy "flutter_dotted: ^1.0.0" to clipboard
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.

example/lib/main.dart

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),
        ),
      ],
    );
  }
}
2
likes
160
points
1.37k
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A lightweight, highly customizable Flutter package to draw dotted and dashed borders, rounded borders, circles, lines, and marching ants around containers.

Homepage
Repository (GitHub)
View/report issues

Topics

#dotted #dashed #border #widget #ui

License

Apache-2.0 (license)

Dependencies

flutter

More

Packages that depend on flutter_dotted