208 lines
7.3 KiB
Dart
208 lines
7.3 KiB
Dart
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<HorizontalScanningAnimation>
|
|
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;
|
|
}
|
|
}
|