Layer voice wave under grid
All checks were successful
Build and deploy Flutter Web / build (push) Successful in 2m15s
All checks were successful
Build and deploy Flutter Web / build (push) Successful in 2m15s
This commit is contained in:
@@ -1359,17 +1359,31 @@ class _VoiceInformationField extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
LayoutBuilder(
|
LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
return CustomPaint(
|
final paintSize = Size(
|
||||||
size: Size(
|
|
||||||
constraints.maxWidth,
|
constraints.maxWidth,
|
||||||
constraints.maxHeight.clamp(300.0, 520.0),
|
constraints.maxHeight.clamp(300.0, 520.0),
|
||||||
|
);
|
||||||
|
return Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
CustomPaint(
|
||||||
|
size: paintSize,
|
||||||
|
painter: _VoiceWaveUnderlayPainter(
|
||||||
|
samples: samples,
|
||||||
|
active: active,
|
||||||
|
liveLevel: liveLevel,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
CustomPaint(
|
||||||
|
size: paintSize,
|
||||||
painter: _VoiceInformationPainter(
|
painter: _VoiceInformationPainter(
|
||||||
samples: samples,
|
samples: samples,
|
||||||
active: active,
|
active: active,
|
||||||
progress: progress,
|
progress: progress,
|
||||||
liveLevel: liveLevel,
|
liveLevel: liveLevel,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -1378,6 +1392,92 @@ class _VoiceInformationField extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _VoiceWaveUnderlayPainter extends CustomPainter {
|
||||||
|
const _VoiceWaveUnderlayPainter({
|
||||||
|
required this.samples,
|
||||||
|
required this.active,
|
||||||
|
required this.liveLevel,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<double> samples;
|
||||||
|
final bool active;
|
||||||
|
final double liveLevel;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
if (samples.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final centerY = size.height * 0.52;
|
||||||
|
final width = math.min(size.width, 420.0);
|
||||||
|
final startX = (size.width - width) / 2;
|
||||||
|
final endX = startX + width;
|
||||||
|
final visibleSamples = samples.length.clamp(24, 96);
|
||||||
|
final sampleStart = math.max(0, samples.length - visibleSamples);
|
||||||
|
final baseline = 12.0 + liveLevel * 34.0;
|
||||||
|
final topPath = Path();
|
||||||
|
final bottomPath = Path();
|
||||||
|
|
||||||
|
for (var index = 0; index < visibleSamples; index++) {
|
||||||
|
final sample = samples[sampleStart + index].clamp(0.0, 1.0);
|
||||||
|
final x = startX + width * (index / math.max(1, visibleSamples - 1));
|
||||||
|
final phase = index * 0.52;
|
||||||
|
final lift = baseline + sample * 112.0;
|
||||||
|
final softWave = math.sin(phase) * (8.0 + liveLevel * 14.0);
|
||||||
|
final y = centerY - lift * 0.5 + softWave;
|
||||||
|
final mirrorY = centerY + lift * 0.5 - softWave;
|
||||||
|
if (index == 0) {
|
||||||
|
topPath.moveTo(x, y);
|
||||||
|
bottomPath.moveTo(x, mirrorY);
|
||||||
|
} else {
|
||||||
|
topPath.lineTo(x, y);
|
||||||
|
bottomPath.lineTo(x, mirrorY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final fillPath = Path.from(topPath)
|
||||||
|
..lineTo(endX, centerY)
|
||||||
|
..lineTo(endX, centerY)
|
||||||
|
..addPath(bottomPath, Offset.zero)
|
||||||
|
..lineTo(startX, centerY)
|
||||||
|
..close();
|
||||||
|
final alpha = active ? 0.10 + liveLevel * 0.16 : 0.05;
|
||||||
|
final fillPaint = Paint()
|
||||||
|
..shader = LinearGradient(
|
||||||
|
begin: Alignment.centerLeft,
|
||||||
|
end: Alignment.centerRight,
|
||||||
|
colors: [
|
||||||
|
const Color(0xFF38F5D3).withValues(alpha: alpha * 0.35),
|
||||||
|
const Color(0xFFFF2D75).withValues(alpha: alpha),
|
||||||
|
const Color(0xFFFF7A90).withValues(alpha: alpha * 0.65),
|
||||||
|
],
|
||||||
|
).createShader(Rect.fromLTWH(startX, 0, width, size.height))
|
||||||
|
..style = PaintingStyle.fill
|
||||||
|
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 18);
|
||||||
|
canvas.drawPath(fillPath, fillPaint);
|
||||||
|
|
||||||
|
final strokePaint = Paint()
|
||||||
|
..color = const Color(
|
||||||
|
0xFFFF6B8A,
|
||||||
|
).withValues(alpha: active ? 0.22 + liveLevel * 0.28 : 0.08)
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = 2.4 + liveLevel * 3.0
|
||||||
|
..strokeCap = StrokeCap.round
|
||||||
|
..strokeJoin = StrokeJoin.round
|
||||||
|
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 5);
|
||||||
|
canvas.drawPath(topPath, strokePaint);
|
||||||
|
canvas.drawPath(bottomPath, strokePaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(covariant _VoiceWaveUnderlayPainter oldDelegate) {
|
||||||
|
return oldDelegate.samples != samples ||
|
||||||
|
oldDelegate.active != active ||
|
||||||
|
oldDelegate.liveLevel != liveLevel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _VoiceInformationPainter extends CustomPainter {
|
class _VoiceInformationPainter extends CustomPainter {
|
||||||
const _VoiceInformationPainter({
|
const _VoiceInformationPainter({
|
||||||
required this.samples,
|
required this.samples,
|
||||||
|
|||||||
Reference in New Issue
Block a user