Skip to content

Instantly share code, notes, and snippets.

@RockerFlower
Created September 16, 2020 03:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save RockerFlower/dc501f25a71c9aceceb83ba4ba198f31 to your computer and use it in GitHub Desktop.
Save RockerFlower/dc501f25a71c9aceceb83ba4ba198f31 to your computer and use it in GitHub Desktop.
Wave
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:math';
import 'dart:ui';
void main() => runApp(WaveDemoApp());
class WaveDemoApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Wave Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: WaveDemoHomePage(title: 'Wave Demo'),
);
}
}
class WaveDemoHomePage extends StatefulWidget {
WaveDemoHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_WaveDemoHomePageState createState() => _WaveDemoHomePageState();
}
class _WaveDemoHomePageState extends State<WaveDemoHomePage> {
_buildCard({
Config config,
Color backgroundColor = Colors.transparent,
DecorationImage backgroundImage,
double height = 152.0,
}) {
return Container(
height: height,
width: double.infinity,
child: Card(
elevation: 12.0,
margin: EdgeInsets.only(right: 16.0, left: 16.0, bottom: 16.0),
clipBehavior: Clip.antiAlias,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16.0))),
child: WaveWidget(
config: config,
backgroundColor: backgroundColor,
backgroundImage: backgroundImage,
size: Size(double.infinity, double.infinity),
waveAmplitude: 0,
),
),
);
}
MaskFilter _blur;
final List<MaskFilter> _blurs = [
null,
MaskFilter.blur(BlurStyle.normal, 10.0),
MaskFilter.blur(BlurStyle.inner, 10.0),
MaskFilter.blur(BlurStyle.outer, 10.0),
MaskFilter.blur(BlurStyle.solid, 16.0),
];
int _blurIndex = 0;
MaskFilter _nextBlur() {
if (_blurIndex == _blurs.length - 1) {
_blurIndex = 0;
} else {
_blurIndex = _blurIndex + 1;
}
_blur = _blurs[_blurIndex];
return _blurs[_blurIndex];
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
elevation: 10.0,
backgroundColor: Colors.blueGrey[800],
actions: <Widget>[
IconButton(
icon: Icon(_blur == null ? Icons.blur_off : Icons.blur_on),
onPressed: () {
setState(() {
_blur = _nextBlur();
});
},
)
],
),
body: Center(
child: ListView(
children: <Widget>[
SizedBox(height: 16.0),
_buildCard(
backgroundColor: Colors.purpleAccent,
config: CustomConfig(
gradients: [
[Colors.red, Color(0xEEF44336)],
[Colors.red[800], Color(0x77E57373)],
[Colors.orange, Color(0x66FF9800)],
[Colors.yellow, Color(0x55FFEB3B)]
],
durations: [35000, 19440, 10800, 6000],
heightPercentages: [0.20, 0.23, 0.25, 0.30],
blur: _blur,
gradientBegin: Alignment.bottomLeft,
gradientEnd: Alignment.topRight,
),
),
_buildCard(
height: 256.0,
backgroundImage: DecorationImage(
image: NetworkImage(
'https://images.unsplash.com/photo-1600107363560-a2a891080c31?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=672&q=80',
),
fit: BoxFit.cover,
colorFilter:
ColorFilter.mode(Colors.white, BlendMode.softLight),
),
config: CustomConfig(
colors: [
Colors.pink[400],
Colors.pink[300],
Colors.pink[200],
Colors.pink[100]
],
durations: [18000, 8000, 5000, 12000],
heightPercentages: [0.85, 0.86, 0.88, 0.90],
blur: _blur,
),
),
_buildCard(
config: CustomConfig(
colors: [
Colors.white70,
Colors.white54,
Colors.white30,
Colors.white24,
],
durations: [32000, 21000, 18000, 5000],
heightPercentages: [0.25, 0.26, 0.28, 0.31],
blur: _blur,
),
backgroundColor: Colors.blue[600]),
],
),
),
);
}
}
class WaveWidget extends StatefulWidget {
final Config config;
final Size size;
final double waveAmplitude;
final double wavePhase;
final double waveFrequency;
final double heightPercentange;
final int duration;
final Color backgroundColor;
final DecorationImage backgroundImage;
final bool isLoop;
WaveWidget({
@required this.config,
@required this.size,
this.waveAmplitude = 20.0,
this.wavePhase = 10.0,
this.waveFrequency = 1.6,
this.heightPercentange = 0.2,
this.duration = 6000,
this.backgroundColor,
this.backgroundImage,
this.isLoop = true,
});
@override
State<StatefulWidget> createState() => _WaveWidgetState();
}
class _WaveWidgetState extends State<WaveWidget> with TickerProviderStateMixin {
List<AnimationController> _waveControllers;
List<Animation<double>> _wavePhaseValues;
List<double> _waveAmplitudes = [];
Map<Animation<double>, AnimationController> valueList;
Timer _endAnimationTimer;
_initAnimations() {
if (widget.config.colorMode == ColorMode.custom) {
_waveControllers =
(widget.config as CustomConfig).durations.map((duration) {
_waveAmplitudes.add(widget.waveAmplitude + 10);
return AnimationController(
vsync: this, duration: Duration(milliseconds: duration));
}).toList();
_wavePhaseValues = _waveControllers.map((controller) {
CurvedAnimation _curve =
CurvedAnimation(parent: controller, curve: Curves.easeInOut);
Animation<double> value = Tween(
begin: widget.wavePhase,
end: 360 + widget.wavePhase,
).animate(
_curve,
);
value.addStatusListener((status) {
switch (status) {
case AnimationStatus.completed:
controller.reverse();
break;
case AnimationStatus.dismissed:
controller.forward();
break;
default:
break;
}
});
controller.forward();
return value;
}).toList();
// If isLoop is false, stop the animation after the specified duration.
if (!widget.isLoop) {
_endAnimationTimer = Timer(Duration(milliseconds: widget.duration), () {
for (AnimationController waveController in _waveControllers) {
waveController.stop();
}
});
}
}
}
_buildPaints() {
List<Widget> paints = [];
if (widget.config.colorMode == ColorMode.custom) {
List<Color> _colors = (widget.config as CustomConfig).colors;
List<List<Color>> _gradients = (widget.config as CustomConfig).gradients;
Alignment begin = (widget.config as CustomConfig).gradientBegin;
Alignment end = (widget.config as CustomConfig).gradientEnd;
for (int i = 0; i < _wavePhaseValues.length; i++) {
paints.add(
Container(
child: CustomPaint(
painter: _CustomWavePainter(
color: _colors == null ? null : _colors[i],
gradient: _gradients == null ? null : _gradients[i],
gradientBegin: begin,
gradientEnd: end,
heightPercentange:
(widget.config as CustomConfig).heightPercentages[i],
repaint: _waveControllers[i],
waveFrequency: widget.waveFrequency,
wavePhaseValue: _wavePhaseValues[i],
waveAmplitude: _waveAmplitudes[i],
blur: (widget.config as CustomConfig).blur,
),
size: widget.size,
),
),
);
}
}
return paints;
}
_disposeAnimations() {
_waveControllers.forEach((controller) {
controller.dispose();
});
}
@override
void initState() {
super.initState();
_initAnimations();
}
@override
void dispose() {
_disposeAnimations();
_endAnimationTimer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: widget.backgroundColor,
image: widget.backgroundImage,
),
child: Stack(
children: _buildPaints(),
),
);
}
}
/// Meta data of layer
class Layer {
final Color color;
final List<Color> gradient;
final MaskFilter blur;
final Path path;
final double amplitude;
final double phase;
Layer({
this.color,
this.gradient,
this.blur,
this.path,
this.amplitude,
this.phase,
});
}
class _CustomWavePainter extends CustomPainter {
final ColorMode colorMode;
final Color color;
final List<Color> gradient;
final Alignment gradientBegin;
final Alignment gradientEnd;
final MaskFilter blur;
double waveAmplitude;
Animation<double> wavePhaseValue;
double waveFrequency;
double heightPercentange;
double _tempA = 0.0;
double _tempB = 0.0;
double viewWidth = 0.0;
Paint _paint = Paint();
_CustomWavePainter(
{this.colorMode,
this.color,
this.gradient,
this.gradientBegin,
this.gradientEnd,
this.blur,
this.heightPercentange,
this.waveFrequency,
this.wavePhaseValue,
this.waveAmplitude,
Listenable repaint})
: super(repaint: repaint);
_setPaths(double viewCenterY, Size size, Canvas canvas) {
Layer _layer = Layer(
path: Path(),
color: color,
gradient: gradient,
blur: blur,
amplitude: (-1.6 + 0.8) * waveAmplitude,
phase: wavePhaseValue.value * 2 + 30,
);
_layer.path.reset();
_layer.path.moveTo(
0.0,
viewCenterY +
_layer.amplitude * _getSinY(_layer.phase, waveFrequency, -1));
for (int i = 1; i < size.width + 1; i++) {
_layer.path.lineTo(
i.toDouble(),
viewCenterY +
_layer.amplitude * _getSinY(_layer.phase, waveFrequency, i));
}
_layer.path.lineTo(size.width, size.height);
_layer.path.lineTo(0.0, size.height);
_layer.path.close();
if (_layer.color != null) {
_paint.color = _layer.color;
}
if (_layer.gradient != null) {
var rect = Offset.zero &
Size(size.width, size.height - viewCenterY * heightPercentange);
_paint.shader = LinearGradient(
begin: gradientBegin == null
? Alignment.bottomCenter
: gradientBegin,
end: gradientEnd == null ? Alignment.topCenter : gradientEnd,
colors: _layer.gradient)
.createShader(rect);
}
if (_layer.blur != null) {
_paint.maskFilter = _layer.blur;
}
_paint.style = PaintingStyle.fill;
canvas.drawPath(_layer.path, _paint);
}
@override
void paint(Canvas canvas, Size size) {
double viewCenterY = size.height * (heightPercentange + 0.1);
viewWidth = size.width;
_setPaths(viewCenterY, size, canvas);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
double _getSinY(
double startradius, double waveFrequency, int currentposition) {
if (_tempA == 0) {
_tempA = pi / viewWidth;
}
if (_tempB == 0) {
_tempB = 2 * pi / 360.0;
}
return (sin(
_tempA * waveFrequency * (currentposition + 1) + startradius * _tempB));
}
}
enum ColorMode {
/// Waves with *single* **color** but different **alpha** and **amplitude**.
single,
/// Waves using *random* **color**, **alpha** and **amplitude**.
random,
/// Waves' colors must be set, and [colors]'s length must equal with [layers]
custom,
}
abstract class Config {
final ColorMode colorMode;
Config({this.colorMode});
void throwNullError(String colorModeStr, String configStr) {
throw FlutterError(
'When using `ColorMode.$colorModeStr`, `$configStr` must be set.');
}
}
class CustomConfig extends Config {
final List<Color> colors;
final List<List<Color>> gradients;
final Alignment gradientBegin;
final Alignment gradientEnd;
final List<int> durations;
final List<double> heightPercentages;
final MaskFilter blur;
CustomConfig({
this.colors,
this.gradients,
this.gradientBegin,
this.gradientEnd,
@required this.durations,
@required this.heightPercentages,
this.blur,
}) : assert(() {
if (colors == null && gradients == null) {
throwNullError('custom', 'colors` or `gradients');
}
return true;
}()),
assert(() {
if (gradients == null &&
(gradientBegin != null || gradientEnd != null)) {
throw FlutterError(
'You set a gradient direction but forgot setting `gradients`.');
}
return true;
}()),
assert(() {
if (durations == null) {
throwNullError('custom', 'durations');
}
return true;
}()),
assert(() {
if (heightPercentages == null) {
throwNullError('custom', 'heightPercentages');
}
return true;
}()),
assert(() {
if (colors != null) {
if (colors.length != durations.length ||
colors.length != heightPercentages.length) {
throw FlutterError(
'Length of `colors`, `durations` and `heightPercentages` must be equal.');
}
}
return true;
}()),
assert(colors == null || gradients == null,
'Cannot provide both colors and gradients.'),
super(colorMode: ColorMode.custom);
}
/// todo
class RandomConfig extends Config {
RandomConfig() : super(colorMode: ColorMode.random);
}
/// todo
class SingleConfig extends Config {
SingleConfig() : super(colorMode: ColorMode.single);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment