animated_background 2.0.0 animated_background: ^2.0.0 copied to clipboard
Animated Backgrounds for Flutter. Easily extended to paint whatever you want on the canvas.
import 'dart:math' as math;
import 'package:animated_background/animated_background.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import './helpers/fade_route.dart';
import './views/space_splash.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Animated Background Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
onPrimary: Colors.black,
primary: Color(0xffe0e0e0),
)),
),
home: new MyHomePage(title: 'Animated Background Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, this.title}) : super(key: key);
final String? title;
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
static const numBehaviours = 6;
// Particles
ParticleType? _particleType = ParticleType.Image;
Image _image = Image.asset('assets/images/star_stroke.png');
ParticleOptions particleOptions = ParticleOptions(
image: Image.asset('assets/images/star_stroke.png'),
baseColor: Colors.blue,
spawnOpacity: 0.0,
opacityChangeRate: 0.25,
minOpacity: 0.1,
maxOpacity: 0.4,
spawnMinSpeed: 30.0,
spawnMaxSpeed: 70.0,
spawnMinRadius: 7.0,
spawnMaxRadius: 15.0,
particleCount: 40,
);
var particlePaint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 1.0;
// Lines
var _lineDirection = LineDirection.Ltr;
int _lineCount = 50;
// Bubbles
BubbleOptions _bubbleOptions = BubbleOptions();
// General Variables
int _behaviourIndex = 0;
Behaviour? _behaviour;
bool _showSettings = false;
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: Text(widget.title!),
actions: <Widget>[
IconButton(
icon: Icon(Icons.settings_input_component),
color: _showSettings ? Colors.amber : Colors.white,
onPressed: () {
setState(() {
_showSettings = !_showSettings;
});
},
),
],
),
body: AnimatedBackground(
behaviour: _behaviour = _buildBehaviour(),
vsync: this,
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: _showSettings ? _buildSettings() : Container(),
),
),
),
);
}
void _onTypeChange(ParticleType? type) {
setState(() {
_particleType = type;
if (_particleType == ParticleType.Image) {
particleOptions = particleOptions.copyWith(
image: _image,
particleCount: math.min(particleOptions.particleCount, 100),
);
} else {
particleOptions = particleOptions.copyWith(
image: null,
);
}
});
}
Widget _buildParticleTypeSelector(ParticleType type) {
return Row(
children: <Widget>[
Radio<ParticleType>(
onChanged: _onTypeChange,
value: type,
groupValue: _particleType,
),
GestureDetector(
child: Text(type.toString().split('.')[1]),
onTap: () => _onTypeChange(type),
),
],
);
}
Widget _buildSettings() {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
children: <Widget>[
ElevatedButton(
child: Text('Next'),
onPressed: () {
setState(() {
_behaviourIndex = (_behaviourIndex + 1) % numBehaviours;
});
},
),
SizedBox(width: 10.0),
Text('Current: ${_behaviourIndex + 1} out of $numBehaviours'),
],
),
SizedBox(height: 10.0),
Text('Behaviour: ${_behaviour.runtimeType.toString()}'),
SizedBox(height: 10.0),
]
..addAll(_behaviour is ParticleBehaviour
? _buildParticleSettings()
: Iterable.empty())
..addAll(_behaviour is RacingLinesBehaviour
? _buildLinesSettings()
: Iterable.empty())
..addAll(_behaviour is BubblesBehaviour
? _buildBubblesSettings()
: Iterable.empty())
..addAll(_behaviour is SpaceBehaviour
? _buildSpaceSettings()
: Iterable.empty()),
);
}
List<Widget> _buildParticleSettings() {
return <Widget>[
Row(
children: <Widget>[
Text('Count:'),
Slider(
value: particleOptions.particleCount.toDouble(),
min: 0.0,
max: _particleType == ParticleType.Image ? 100.0 : 1000.0,
divisions: 100,
onChanged: (value) {
setState(() {
particleOptions = particleOptions.copyWith(
particleCount: value.floor(),
);
});
},
),
Text(
'${particleOptions.particleCount}',
style: Theme.of(context).textTheme.headline4,
),
],
),
Row(
children: <Widget>[
Text('Spawn opacity:'),
Slider(
value: particleOptions.spawnOpacity,
min: 0.0,
max: 1.0,
divisions: 10,
onChanged: (value) {
setState(() {
particleOptions = particleOptions.copyWith(
spawnOpacity: value,
);
});
},
),
Text('${particleOptions.spawnOpacity.toStringAsFixed(1)}'),
],
),
Row(
children: <Widget>[
Text('Opacity rate:'),
Slider(
value: particleOptions.opacityChangeRate,
min: -2.0,
max: 2.0,
divisions: 80,
onChanged: (value) {
setState(() {
particleOptions = particleOptions.copyWith(
opacityChangeRate: value,
);
});
},
),
Text('${particleOptions.opacityChangeRate.toStringAsFixed(2)}'),
],
),
Row(
children: <Widget>[
Text('Min opacity:'),
Slider(
value: particleOptions.minOpacity,
min: 0.0,
max: 1.0,
divisions: 20,
onChanged: (value) {
setState(() {
particleOptions = particleOptions.copyWith(
minOpacity: value,
maxOpacity: math.max(particleOptions.maxOpacity, value),
);
});
},
),
Text('${particleOptions.minOpacity.toStringAsFixed(2)}'),
],
),
Row(
children: <Widget>[
Text('Max opacity:'),
Slider(
value: particleOptions.maxOpacity,
min: 0.0,
max: 1.0,
divisions: 20,
onChanged: (value) {
setState(() {
particleOptions = particleOptions.copyWith(
maxOpacity: value,
minOpacity: math.min(particleOptions.minOpacity, value),
);
});
},
),
Text('${particleOptions.maxOpacity.toStringAsFixed(2)}'),
],
),
Row(
children: <Widget>[
Text('Min radius:'),
Slider(
value: particleOptions.spawnMinRadius,
min: 1.0,
max: 100.0,
divisions: 99,
onChanged: (value) {
setState(() {
particleOptions = particleOptions.copyWith(
spawnMinRadius: value,
spawnMaxRadius:
math.max(particleOptions.spawnMaxRadius, value),
);
});
},
),
Text('${particleOptions.spawnMinRadius.toInt()}'),
],
),
Row(
children: <Widget>[
Text('Max radius:'),
Slider(
value: particleOptions.spawnMaxRadius,
min: 1.0,
max: 100.0,
divisions: 99,
onChanged: (value) {
setState(() {
particleOptions = particleOptions.copyWith(
spawnMaxRadius: value,
spawnMinRadius:
math.min(particleOptions.spawnMinRadius, value),
);
});
},
),
Text('${particleOptions.spawnMaxRadius.toInt()}'),
],
),
Row(
children: <Widget>[
Text('Min speed:'),
Slider(
value: particleOptions.spawnMinSpeed,
min: 1.0,
max: 100.0,
divisions: 99,
onChanged: (value) {
setState(() {
particleOptions = particleOptions.copyWith(
spawnMinSpeed: value,
spawnMaxSpeed: math.max(particleOptions.spawnMaxSpeed, value),
);
});
},
),
Text('${particleOptions.spawnMinSpeed.toInt()}'),
],
),
Row(
children: <Widget>[
Text('Max speed:'),
Slider(
value: particleOptions.spawnMaxSpeed,
min: 1.0,
max: 100.0,
divisions: 99,
onChanged: (value) {
setState(() {
particleOptions = particleOptions.copyWith(
spawnMaxSpeed: value,
spawnMinSpeed: math.min(particleOptions.spawnMinSpeed, value),
);
});
},
),
Text('${particleOptions.spawnMaxSpeed.toInt()}'),
],
),
SizedBox(height: 10.0),
Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
_buildParticleTypeSelector(ParticleType.Shape),
_buildParticleTypeSelector(ParticleType.Image),
],
),
_buildTypeSettings(),
];
}
Widget _buildTypeSettings() {
switch (_particleType!) {
case ParticleType.Image:
return Column(
children: <Widget>[
Row(
children: <Widget>[
_buildImageSelector(
Image.asset('assets/images/star_stroke.png')),
_buildImageSelector(Image.asset('assets/images/icy_logo.png')),
ElevatedButton(
child: Text('Clipboard'),
onPressed: () {
Clipboard.getData('text/plain')
.then((ClipboardData? value) {
if (value == null) return;
setState(() {
_image = Image.network(value.text!);
particleOptions = particleOptions.copyWith(
image: _image,
);
});
});
},
),
],
),
],
);
case ParticleType.Shape:
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
children: <Widget>[
Checkbox(
onChanged: (value) => setState(() {
particlePaint.style =
value! ? PaintingStyle.fill : PaintingStyle.stroke;
}),
value: particlePaint.style == PaintingStyle.fill,
),
Text('Fill Shape')
],
),
Row(
children: <Widget>[
Text('Stroke Width:'),
Slider(
value: particlePaint.strokeWidth,
min: 1.0,
max: 50.0,
divisions: 49,
onChanged: (value) =>
setState(() => particlePaint.strokeWidth = value),
),
Text('${particlePaint.strokeWidth.toInt()}'),
],
),
],
);
}
}
Widget _buildImageSelector(Image image) {
return InkWell(
child: SizedBox(
child: Container(
decoration: BoxDecoration(
border: Border.all(
width: 1.0,
color: _image.image == image.image
? Colors.amber
: Colors.transparent,
),
),
child: image,
),
width: 40.0,
height: 40.0,
),
onTap: () => setState(() {
_image = image;
particleOptions = particleOptions.copyWith(
image: _image,
);
}),
);
}
List<Widget> _buildLinesSettings() {
return <Widget>[
Row(
children: <Widget>[
Text('Count:'),
Slider(
value: _lineCount.toDouble(),
min: 0.0,
max: 500.0,
divisions: 100,
onChanged: (value) {
setState(() {
_lineCount = value.floor();
});
},
),
Text(
'$_lineCount',
style: Theme.of(context).textTheme.headline4,
),
],
),
Row(
children: <Widget>[
ElevatedButton(
child: Text('Next Direction'),
onPressed: () {
setState(() {
_lineDirection =
LineDirection.values[(_lineDirection.index + 1) % 4];
});
},
),
],
),
];
}
List<Widget> _buildBubblesSettings() {
return <Widget>[
Row(
children: <Widget>[
Text('Count:'),
Slider(
value: _bubbleOptions.bubbleCount.toDouble(),
min: 0.0,
max: 400.0,
divisions: 400,
onChanged: (value) {
setState(() {
_bubbleOptions = _bubbleOptions.copyWith(
bubbleCount: value.floor(),
);
});
},
),
Text(
'${_bubbleOptions.bubbleCount}',
style: Theme.of(context).textTheme.headline4,
),
],
),
Row(
children: <Widget>[
Text('Min radius:'),
Slider(
value: _bubbleOptions.minTargetRadius,
min: 1.0,
max: 100.0,
divisions: 99,
onChanged: (value) {
setState(() {
_bubbleOptions = _bubbleOptions.copyWith(
minTargetRadius: value,
maxTargetRadius:
math.max(_bubbleOptions.maxTargetRadius, value),
);
});
},
),
Text('${_bubbleOptions.minTargetRadius.toInt()}'),
],
),
Row(
children: <Widget>[
Text('Max radius:'),
Slider(
value: _bubbleOptions.maxTargetRadius,
min: 1.0,
max: 100.0,
divisions: 99,
onChanged: (value) {
setState(() {
_bubbleOptions = _bubbleOptions.copyWith(
maxTargetRadius: value,
minTargetRadius:
math.min(_bubbleOptions.minTargetRadius, value),
);
});
},
),
Text('${_bubbleOptions.maxTargetRadius.toInt()}'),
],
),
Row(
children: <Widget>[
Text('Growth Rate:'),
Slider(
value: _bubbleOptions.growthRate,
min: 0.0,
max: 400.0,
divisions: 401,
onChanged: (value) {
setState(() {
_bubbleOptions = _bubbleOptions.copyWith(
growthRate: value,
);
});
},
),
Text('${_bubbleOptions.growthRate.toInt()}'),
],
),
Row(
children: <Widget>[
Text('Pop Rate:'),
Slider(
value: _bubbleOptions.popRate,
min: 0.0,
max: 400.0,
divisions: 401,
onChanged: (value) {
setState(() {
_bubbleOptions = _bubbleOptions.copyWith(
popRate: value,
);
});
},
),
Text('${_bubbleOptions.popRate.toInt()}'),
],
),
];
}
List<Widget> _buildSpaceSettings() {
return <Widget>[
Row(
children: <Widget>[
ElevatedButton(
child: Text('Launch Example Splash'),
onPressed: () {
setState(() {
Navigator.of(context).push(SimpleFadeRoute(
child: SpaceSplash(),
));
});
},
),
],
),
];
}
Behaviour _buildBehaviour() {
switch (_behaviourIndex) {
case 0:
return RandomParticleBehaviour(
options: particleOptions,
paint: particlePaint,
);
case 1:
return RainParticleBehaviour(
options: particleOptions,
paint: particlePaint,
enabled: !_showSettings,
);
case 2:
return RectanglesBehaviour();
case 3:
return RacingLinesBehaviour(
direction: _lineDirection,
numLines: _lineCount,
);
case 4:
return BubblesBehaviour(
options: _bubbleOptions,
);
case 5:
return SpaceBehaviour();
}
return RandomParticleBehaviour(
options: particleOptions,
paint: particlePaint,
);
}
}
enum ParticleType {
Shape,
Image,
}
class RainParticleBehaviour extends RandomParticleBehaviour {
static math.Random random = math.Random();
bool enabled;
RainParticleBehaviour({
ParticleOptions options = const ParticleOptions(),
Paint? paint,
this.enabled = true,
}) : super(options: options, paint: paint);
@override
void initPosition(Particle p) {
p.cx = random.nextDouble() * size!.width;
if (p.cy == 0.0)
p.cy = random.nextDouble() * size!.height;
else
p.cy = random.nextDouble() * size!.width * 0.2;
}
@override
void initDirection(Particle p, double speed) {
double dirX = (random.nextDouble() - 0.5);
double dirY = random.nextDouble() * 0.5 + 0.5;
double magSq = dirX * dirX + dirY * dirY;
double mag = magSq <= 0 ? 1 : math.sqrt(magSq);
p.dx = dirX / mag * speed;
p.dy = dirY / mag * speed;
}
@override
Widget builder(
BuildContext context, BoxConstraints constraints, Widget child) {
return GestureDetector(
onPanUpdate: enabled
? (details) => _updateParticles(context, details.globalPosition)
: null,
onTapDown: enabled
? (details) => _updateParticles(context, details.globalPosition)
: null,
child: ConstrainedBox(
// necessary to force gesture detector to cover screen
constraints: BoxConstraints(
minHeight: double.infinity, minWidth: double.infinity),
child: super.builder(context, constraints, child),
),
);
}
void _updateParticles(BuildContext context, Offset offsetGlobal) {
RenderBox renderBox = context.findRenderObject() as RenderBox;
var offset = renderBox.globalToLocal(offsetGlobal);
particles!.forEach((particle) {
var delta = (Offset(particle.cx, particle.cy) - offset);
if (delta.distanceSquared < 70 * 70) {
var speed = particle.speed;
var mag = delta.distance;
speed *= (70 - mag) / 70.0 * 2.0 + 0.5;
speed = math.max(
options.spawnMinSpeed, math.min(options.spawnMaxSpeed, speed));
particle.dx = delta.dx / mag * speed;
particle.dy = delta.dy / mag * speed;
}
});
}
}