before_after_slider 1.0.1 copy "before_after_slider: ^1.0.1" to clipboard
before_after_slider: ^1.0.1 copied to clipboard

Compare before/after images or widgets with a smooth draggable divider, pinch zoom, pan, and customizable overlay UI.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:before_after_slider/before_after_slider.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Before/After Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  int _selectedIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Before/After Demo'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: IndexedStack(
        index: _selectedIndex,
        children: const [
          BeforeAfterImageDemo(),
          BeforeAfterLayoutDemo(),
          CustomOverlayDemo(),
        ],
      ),
      bottomNavigationBar: NavigationBar(
        selectedIndex: _selectedIndex,
        onDestinationSelected: (index) {
          setState(() {
            _selectedIndex = index;
          });
        },
        destinations: const [
          NavigationDestination(
            icon: Icon(Icons.image),
            label: 'Image',
          ),
          NavigationDestination(
            icon: Icon(Icons.layers),
            label: 'Layout',
          ),
          NavigationDestination(
            icon: Icon(Icons.brush),
            label: 'Custom',
          ),
        ],
      ),
    );
  }
}

/// Demo for BeforeAfterImage widget.
class BeforeAfterImageDemo extends StatefulWidget {
  const BeforeAfterImageDemo({super.key});

  @override
  State<BeforeAfterImageDemo> createState() => _BeforeAfterImageDemoState();
}

class _BeforeAfterImageDemoState extends State<BeforeAfterImageDemo> {
  double _progress = 0.5;
  ContentOrder _contentOrder = ContentOrder.beforeAfter;
  BoxFit _fit = BoxFit.contain;
  final ZoomController _zoomController = ZoomController();

  @override
  void initState() {
    super.initState();
    _zoomController.addListener(() {
      setState(() {});
    });
  }

  @override
  void dispose() {
    _zoomController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          // Before/After Image widget
          AspectRatio(
            aspectRatio: 4 / 3,
            child: Container(
              decoration: BoxDecoration(
                border: Border.all(color: Colors.grey.shade300),
                borderRadius: BorderRadius.circular(12),
              ),
              clipBehavior: Clip.antiAlias,
              child: BeforeAfterImage(
                beforeImage: const AssetImage('assets/before.png'),
                afterImage: const AssetImage('assets/after.png'),
                progress: _progress,
                onProgressChanged: (value) {
                  setState(() {
                    _progress = value;
                  });
                },
                contentOrder: _contentOrder,
                fit: _fit,
                zoomController: _zoomController,
                overlayStyle: const OverlayStyle(
                  dividerColor: Colors.white,
                  dividerWidth: 2,
                  thumbSize: 40,
                  thumbElevation: 4,
                ),
              ),
            ),
          ),
          const SizedBox(height: 24),

          // Progress slider
          Text('Progress: ${(_progress * 100).toStringAsFixed(0)}%'),
          Slider(
            value: _progress,
            onChanged: (value) {
              setState(() {
                _progress = value;
              });
            },
          ),
          const SizedBox(height: 16),

          // Content order toggle
          Row(
            children: [
              const Text('Content Order:'),
              const SizedBox(width: 16),
              ChoiceChip(
                label: const Text('Before/After'),
                selected: _contentOrder == ContentOrder.beforeAfter,
                onSelected: (selected) {
                  if (selected) {
                    setState(() {
                      _contentOrder = ContentOrder.beforeAfter;
                    });
                  }
                },
              ),
              const SizedBox(width: 8),
              ChoiceChip(
                label: const Text('After/Before'),
                selected: _contentOrder == ContentOrder.afterBefore,
                onSelected: (selected) {
                  if (selected) {
                    setState(() {
                      _contentOrder = ContentOrder.afterBefore;
                    });
                  }
                },
              ),
            ],
          ),
          const SizedBox(height: 16),

          // BoxFit selector
          Wrap(
            spacing: 8,
            runSpacing: 8,
            children: [
              const Text('Fit: '),
              for (final fit in [
                BoxFit.contain,
                BoxFit.cover,
                BoxFit.fill,
                BoxFit.fitWidth,
                BoxFit.fitHeight,
              ])
                ChoiceChip(
                  label: Text(fit.name),
                  selected: _fit == fit,
                  onSelected: (selected) {
                    if (selected) {
                      setState(() {
                        _fit = fit;
                      });
                    }
                  },
                ),
            ],
          ),
          const SizedBox(height: 24),

          Card(
            color: Colors.blue.shade50,
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text(
                    'Debug Info',
                    style: TextStyle(fontWeight: FontWeight.bold),
                  ),
                  const SizedBox(height: 8),
                  Text('Zoom: ${_zoomController.zoom.toStringAsFixed(2)}x'),
                  Text(
                      'Pan: (${_zoomController.pan.dx.toStringAsFixed(1)}, ${_zoomController.pan.dy.toStringAsFixed(1)})'),
                  Text('Progress: ${(_progress * 100).toStringAsFixed(1)}%'),
                ],
              ),
            ),
          ),
          const SizedBox(height: 8),
          const Card(
            child: Padding(
              padding: EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    'Instructions',
                    style: TextStyle(fontWeight: FontWeight.bold),
                  ),
                  SizedBox(height: 8),
                  Text('• Drag the thumb to change progress'),
                  Text('• Pinch to zoom'),
                  Text('• Double-tap to reset zoom'),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}

/// Demo for BeforeAfterLayout widget.
class BeforeAfterLayoutDemo extends StatelessWidget {
  const BeforeAfterLayoutDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          const Text(
            'Compare any widgets',
            style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 16),
          Expanded(
            child: Container(
              decoration: BoxDecoration(
                border: Border.all(color: Colors.grey.shade300),
                borderRadius: BorderRadius.circular(12),
              ),
              clipBehavior: Clip.antiAlias,
              child: BeforeAfterLayout(
                beforeChild: Container(
                  color: Colors.blue.shade100,
                  child: const Center(
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Icon(Icons.looks_one, size: 64, color: Colors.blue),
                        SizedBox(height: 16),
                        Text(
                          'Material Design 2',
                          style: TextStyle(
                            fontSize: 24,
                            fontWeight: FontWeight.bold,
                            color: Colors.blue,
                          ),
                        ),
                        SizedBox(height: 8),
                        Text('Classic Flutter widgets'),
                      ],
                    ),
                  ),
                ),
                afterChild: Container(
                  color: Colors.purple.shade100,
                  child: const Center(
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Icon(Icons.looks_two, size: 64, color: Colors.purple),
                        SizedBox(height: 16),
                        Text(
                          'Material Design 3',
                          style: TextStyle(
                            fontSize: 24,
                            fontWeight: FontWeight.bold,
                            color: Colors.purple,
                          ),
                        ),
                        SizedBox(height: 8),
                        Text('Modern Flutter widgets'),
                      ],
                    ),
                  ),
                ),
                overlayStyle: const OverlayStyle(
                  dividerColor: Colors.deepPurple,
                  dividerWidth: 3,
                  thumbBackgroundColor: Colors.deepPurple,
                  thumbIconColor: Colors.white,
                  thumbSize: 48,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

/// Demo for custom overlay.
class CustomOverlayDemo extends StatefulWidget {
  const CustomOverlayDemo({super.key});

  @override
  State<CustomOverlayDemo> createState() => _CustomOverlayDemoState();
}

class _CustomOverlayDemoState extends State<CustomOverlayDemo> {
  double _progress = 0.5;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          const Text(
            'Custom Overlay',
            style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 16),
          Expanded(
            child: Container(
              decoration: BoxDecoration(
                border: Border.all(color: Colors.grey.shade300),
                borderRadius: BorderRadius.circular(12),
              ),
              clipBehavior: Clip.antiAlias,
              child: BeforeAfterImage(
                beforeImage: const AssetImage('assets/before_alt.png'),
                afterImage: const AssetImage('assets/after_alt.png'),
                progress: _progress,
                onProgressChanged: (value) {
                  setState(() {
                    _progress = value;
                  });
                },
                beforeLabel: Container(
                  margin: const EdgeInsets.all(8),
                  padding: const EdgeInsets.symmetric(
                    horizontal: 12,
                    vertical: 6,
                  ),
                  decoration: BoxDecoration(
                    color: Colors.black54,
                    borderRadius: BorderRadius.circular(20),
                  ),
                  child: const Text(
                    'Blurred',
                    style: TextStyle(color: Colors.white),
                  ),
                ),
                afterLabel: Container(
                  margin: const EdgeInsets.all(8),
                  padding: const EdgeInsets.symmetric(
                    horizontal: 12,
                    vertical: 6,
                  ),
                  decoration: BoxDecoration(
                    color: Colors.black54,
                    borderRadius: BorderRadius.circular(20),
                  ),
                  child: const Text(
                    'Original',
                    style: TextStyle(color: Colors.white),
                  ),
                ),
                overlay: (size, position) {
                  return CustomPaint(
                    size: size,
                    painter: _GradientDividerPainter(
                      position: position.dx,
                    ),
                  );
                },
              ),
            ),
          ),
          const SizedBox(height: 16),
          const Card(
            child: Padding(
              padding: EdgeInsets.all(16),
              child: Text(
                'This demo shows a custom gradient overlay instead of the default divider.',
              ),
            ),
          ),
        ],
      ),
    );
  }
}

/// Custom painter that draws a gradient divider.
class _GradientDividerPainter extends CustomPainter {
  _GradientDividerPainter({required this.position});

  final double position;

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..shader = const LinearGradient(
        begin: Alignment.topCenter,
        end: Alignment.bottomCenter,
        colors: [
          Colors.purple,
          Colors.blue,
          Colors.cyan,
        ],
      ).createShader(Rect.fromLTWH(position - 2, 0, 4, size.height))
      ..strokeWidth = 4
      ..style = PaintingStyle.stroke;

    canvas.drawLine(
      Offset(position, 0),
      Offset(position, size.height),
      paint,
    );

    // Draw thumb
    final thumbPaint = Paint()
      ..shader = const LinearGradient(
        colors: [Colors.purple, Colors.blue],
      ).createShader(
        Rect.fromCircle(center: Offset(position, size.height / 2), radius: 20),
      );

    canvas.drawCircle(
      Offset(position, size.height / 2),
      20,
      thumbPaint,
    );

    // Draw arrows
    final arrowPaint = Paint()
      ..color = Colors.white
      ..strokeWidth = 2
      ..style = PaintingStyle.stroke
      ..strokeCap = StrokeCap.round;

    final centerY = size.height / 2;

    // Left arrow
    canvas.drawLine(
      Offset(position - 8, centerY),
      Offset(position - 4, centerY - 5),
      arrowPaint,
    );
    canvas.drawLine(
      Offset(position - 8, centerY),
      Offset(position - 4, centerY + 5),
      arrowPaint,
    );

    // Right arrow
    canvas.drawLine(
      Offset(position + 8, centerY),
      Offset(position + 4, centerY - 5),
      arrowPaint,
    );
    canvas.drawLine(
      Offset(position + 8, centerY),
      Offset(position + 4, centerY + 5),
      arrowPaint,
    );
  }

  @override
  bool shouldRepaint(_GradientDividerPainter oldDelegate) {
    return position != oldDelegate.position;
  }
}
1
likes
0
points
235
downloads

Publisher

verified publisherbytecabins.com

Weekly Downloads

Compare before/after images or widgets with a smooth draggable divider, pinch zoom, pan, and customizable overlay UI.

Repository (GitHub)
View/report issues

Topics

#image-comparison #before-after #slider #zoom #pan

License

unknown (license)

Dependencies

flutter

More

Packages that depend on before_after_slider