import 'dart:math'; import 'package:flutter/material.dart'; class HorizontalScanningAnimation extends StatefulWidget { final bool isScanning; // Add this to control the animation final Color waveColor; final double height; const HorizontalScanningAnimation({ super.key, required this.isScanning, // Make it required this.waveColor = Colors.lightBlueAccent, this.height = 50.0, }); @override _HorizontalScanningAnimationState createState() => _HorizontalScanningAnimationState(); } class _HorizontalScanningAnimationState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; @override void initState() { super.initState(); _controller = AnimationController( vsync: this, duration: const Duration(seconds: 2), ); // Start repeating only if initially scanning if (widget.isScanning) { _controller.repeat(); } } @override void dispose() { _controller.dispose(); super.dispose(); } @override void didUpdateWidget(covariant HorizontalScanningAnimation oldWidget) { super.didUpdateWidget(oldWidget); if (widget.isScanning != oldWidget.isScanning) { if (widget.isScanning) { // Start or resume repeating if (!_controller.isAnimating) { // If stopped previously, reset before repeating for a clean start // Though, repeat() should handle restarting if stopped. Testing needed. // _controller.reset(); // Optional: uncomment if repeat doesn't restart smoothly _controller.repeat(); } } else { // Stop repeating, but let the current animation cycle finish visually if (_controller.isAnimating) { _controller.stop( canceled: false); // Use canceled: false to let it finish the current tick } } } } @override Widget build(BuildContext context) { // Only build the painter if the controller is active or was recently stopped // This prevents drawing when completely idle. Check if value is changing or non-zero. // Or simply rely on the AnimatedBuilder which won't rebuild if controller is idle at 0.0 return SizedBox( height: widget.height, width: double.infinity, child: AnimatedBuilder( animation: _controller, builder: (context, child) { return CustomPaint( painter: _HorizontalWavePainter( progress: _controller.value, waveColor: widget.waveColor, ), ); }, ), ); } } class _HorizontalWavePainter extends CustomPainter { final double progress; // Animation value from 0.0 to 1.0 final Color waveColor; final int waveCount = 2; // Number of waves visible at once final double waveAmplitude = 10.0; // Max height deviation of the wave _HorizontalWavePainter({required this.progress, required this.waveColor}); @override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = waveColor.withValues(alpha: 0.6) // Semi-transparent waves ..style = PaintingStyle.fill; final centerY = size.height / 2; final width = size.width; // Draw multiple waves propagating outwards for (int i = 0; i < waveCount; i++) { // Calculate the phase offset for each wave based on progress and index // This creates the effect of waves moving outwards double waveProgress = (progress + i / waveCount) % 1.0; // Use an easing curve for smoother expansion double easedProgress = Curves.easeInOutSine.transform(waveProgress); // Calculate the current horizontal position (expanding from center) // The wave starts narrow and expands outwards double currentWidth = width * easedProgress * 0.8; // Max 80% width expansion double startX = (width / 2) - (currentWidth / 2); double endX = (width / 2) + (currentWidth / 2); // Calculate opacity based on progress (fade in and out) double opacity; if (waveProgress < 0.1) { opacity = waveProgress / 0.1; // Fade in } else if (waveProgress > 0.8) { opacity = (1.0 - waveProgress) / 0.2; // Fade out } else { opacity = 1.0; } opacity = max(0.0, opacity); // Clamp opacity if (opacity <= 0.0 || currentWidth < 5) continue; // Skip drawing if invisible or too small // Create the wave path final path = Path(); path.moveTo(startX, centerY); // Calculate points for the sine wave shape within the current width const int segments = 50; // Number of segments for the curve for (int j = 0; j <= segments; j++) { double segmentProgress = j / segments; double x = startX + currentWidth * segmentProgress; // Apply sine wave based on segment progress and overall animation progress // Multiply by (1 - easedProgress) to reduce amplitude as it expands double yOffset = waveAmplitude * sin(segmentProgress * 2 * pi + progress * 4 * pi) * (1 - easedProgress * 0.8) * // Reduce amplitude as it expands opacity; // Apply opacity effect to amplitude too path.lineTo(x, centerY + yOffset); } // Draw a filled shape (like a lens flare or horizontal bar) // Adjust thickness based on easedProgress (thicker in the middle, thinner at ends) double thickness = waveAmplitude * (1 - easedProgress * 0.9) * opacity * 0.5; paint.color = waveColor.withValues( alpha: opacity * 0.5); // Update paint color with opacity // Simplified: Draw a rectangle that pulses // More complex shapes could be drawn here using path.arcTo or path.quadraticBezierTo // For simplicity, let's use a slightly blurred rectangle effect final rectPath = Path() ..addRRect(RRect.fromRectAndRadius( Rect.fromCenter( center: Offset(width / 2, centerY), width: currentWidth, height: thickness * 2), Radius.circular(thickness))); // Apply a blur effect final blurPaint = Paint() ..color = waveColor.withValues(alpha: opacity * 0.4) ..maskFilter = MaskFilter.blur( BlurStyle.normal, thickness * 1.5); // Blur based on thickness // Draw the blurred shape canvas.drawPath(rectPath, blurPaint); // Draw a slightly smaller, less opaque shape on top for highlight final highlightPaint = Paint() ..color = waveColor.withValues(alpha: opacity * 0.7) ..style = PaintingStyle.fill; final highlightRectPath = Path() ..addRRect(RRect.fromRectAndRadius( Rect.fromCenter( center: Offset(width / 2, centerY), width: currentWidth * 0.95, height: thickness * 1.5), Radius.circular(thickness * 0.8))); canvas.drawPath(highlightRectPath, highlightPaint); // Old Path drawing - keep if rectangle isn't desired // paint.color = waveColor.withValues(alpha: opacity * 0.5); // Apply opacity // canvas.drawPath(path, paint); } } @override bool shouldRepaint(covariant _HorizontalWavePainter oldDelegate) { // Repaint whenever the animation progress or color changes return oldDelegate.progress != progress || oldDelegate.waveColor != waveColor; } }