|
|
@@ -0,0 +1,245 @@ |
|
|
|
|
|
import 'dart:async'; |
|
|
import 'dart:ui'; |
|
|
|
|
|
import 'package:flutter/material.dart'; |
|
|
import 'dart:math' as math; |
|
|
|
|
|
void main() { |
|
|
runApp(MyApp()); |
|
|
} |
|
|
|
|
|
num degreesToRads(num deg) { |
|
|
return (deg * math.pi) / 180.0; |
|
|
} |
|
|
|
|
|
double randD(double min, double max) => |
|
|
math.Random().nextDouble() * (max - min) + min; |
|
|
|
|
|
class Orbit { |
|
|
final List<Electron> electrons; |
|
|
final angle; |
|
|
|
|
|
Orbit({ |
|
|
@required this.electrons, |
|
|
@required this.angle, |
|
|
}); |
|
|
} |
|
|
|
|
|
extension OrbitsExtension on List<Orbit> { |
|
|
void updateElectronsPosition() { |
|
|
this.forEach((orbit) { |
|
|
orbit.electrons.forEach((electron) { |
|
|
electron.move(); |
|
|
}); |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
class Electron { |
|
|
double speed; |
|
|
|
|
|
double currentSize; |
|
|
double targetSize; |
|
|
|
|
|
Color currentColor; |
|
|
Color targetColor; |
|
|
|
|
|
double _positionPercent = 0; |
|
|
|
|
|
double get positionPercent { |
|
|
return _positionPercent; |
|
|
} |
|
|
|
|
|
Electron({ |
|
|
@required this.currentSize, |
|
|
@required this.targetSize, |
|
|
@required this.currentColor, |
|
|
@required this.targetColor, |
|
|
@required this.speed, |
|
|
}); |
|
|
|
|
|
void move() { |
|
|
if (_positionPercent >= 1.0) { |
|
|
_positionPercent = 0; |
|
|
} |
|
|
_positionPercent += speed; |
|
|
} |
|
|
|
|
|
static Electron random() { |
|
|
const colors = [ |
|
|
Colors.greenAccent, Colors.redAccent, Colors.cyanAccent, Colors.purpleAccent, |
|
|
Colors.yellowAccent, |
|
|
]; |
|
|
final size = randD(5, 7); |
|
|
final initialSize = size * 8; |
|
|
final color = colors[math.Random().nextInt(colors.length)]; |
|
|
return Electron( |
|
|
currentSize: initialSize, |
|
|
targetSize: size, |
|
|
currentColor: color.withOpacity(0.1), |
|
|
targetColor: color, |
|
|
speed: randD(0.008, 0.012), |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
class MyApp extends StatelessWidget { |
|
|
// This widget is the root of your application. |
|
|
@override |
|
|
Widget build(BuildContext context) { |
|
|
return MaterialApp( |
|
|
title: 'Flutter Demo', |
|
|
theme: ThemeData( |
|
|
primarySwatch: Colors.yellow, |
|
|
visualDensity: VisualDensity.adaptivePlatformDensity, |
|
|
), |
|
|
home: HomePage(), |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
class HomePage extends StatefulWidget { |
|
|
const HomePage({Key key}) : super(key: key); |
|
|
|
|
|
@override |
|
|
_HomePageState createState() => _HomePageState(); |
|
|
} |
|
|
|
|
|
class _HomePageState extends State<HomePage> { |
|
|
List<Orbit> _orbits = [ |
|
|
Orbit(electrons: [], angle: 0.0), |
|
|
Orbit(electrons: [], angle: 60.0), |
|
|
Orbit(electrons: [], angle: 120.0), |
|
|
]; |
|
|
|
|
|
Widget build(BuildContext context) { |
|
|
return Scaffold( |
|
|
backgroundColor: Color(0xFF191636), |
|
|
body: Center( |
|
|
child: AtomWidget( |
|
|
orbits: _orbits, |
|
|
), |
|
|
), |
|
|
floatingActionButton: FloatingActionButton( |
|
|
onPressed: () { |
|
|
setState(() { |
|
|
_orbits[math.Random().nextInt(_orbits.length)].electrons.add(Electron.random()); |
|
|
}); |
|
|
}, |
|
|
child: Icon(Icons.add), |
|
|
), |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
class AtomWidget extends StatefulWidget { |
|
|
final List<Orbit> orbits; |
|
|
|
|
|
const AtomWidget({Key key, @required this.orbits}) : super(key: key); |
|
|
|
|
|
@override |
|
|
_AtomWidgetState createState() => _AtomWidgetState(); |
|
|
} |
|
|
|
|
|
class _AtomWidgetState extends State<AtomWidget> with SingleTickerProviderStateMixin { |
|
|
Timer t; |
|
|
|
|
|
@override |
|
|
void initState() { |
|
|
super.initState(); |
|
|
t = Timer.periodic(Duration(milliseconds: 16), (timer) { |
|
|
setState(() { |
|
|
widget.orbits.updateElectronsPosition(); |
|
|
}); |
|
|
}); |
|
|
} |
|
|
|
|
|
@override |
|
|
Widget build(BuildContext context) { |
|
|
final atomSize = (MediaQuery.of(context).size.shortestSide / 3) * 2; |
|
|
return Stack( |
|
|
children: [ |
|
|
...widget.orbits |
|
|
.map( |
|
|
(orbit) => Center( |
|
|
child: Transform.rotate( |
|
|
angle: degreesToRads(orbit.angle), |
|
|
child: CustomPaint( |
|
|
painter: _OrbitPainter(orbit), |
|
|
size: Size(atomSize, atomSize), |
|
|
), |
|
|
), |
|
|
), |
|
|
) |
|
|
.toList(), |
|
|
Center( |
|
|
child: Container( |
|
|
width: atomSize / 10, |
|
|
height: atomSize / 10, |
|
|
decoration: BoxDecoration( |
|
|
shape: BoxShape.circle, |
|
|
gradient: LinearGradient( |
|
|
colors: [ |
|
|
Color(0xffffc560), |
|
|
Color(0xffff593b), |
|
|
], |
|
|
begin: Alignment.bottomLeft, |
|
|
end: Alignment.topRight, |
|
|
)), |
|
|
), |
|
|
) |
|
|
], |
|
|
); |
|
|
} |
|
|
|
|
|
@override |
|
|
void dispose() { |
|
|
t.cancel(); |
|
|
super.dispose(); |
|
|
} |
|
|
} |
|
|
|
|
|
class _OrbitPainter extends CustomPainter { |
|
|
final Orbit orbit; |
|
|
|
|
|
_OrbitPainter(this.orbit); |
|
|
|
|
|
@override |
|
|
void paint(Canvas canvas, Size size) { |
|
|
final center = Offset(size.width / 2, size.height / 2); |
|
|
Path orbitPath = new Path(); |
|
|
|
|
|
final width = size.shortestSide; |
|
|
final height = width * 0.24; |
|
|
|
|
|
orbitPath.addOval(Rect.fromCenter(center: center, width: width, height: height)); |
|
|
|
|
|
canvas.drawPath( |
|
|
orbitPath, |
|
|
Paint() |
|
|
..color = Color(0xFF26224f) |
|
|
..style = PaintingStyle.stroke |
|
|
..strokeWidth = 1, |
|
|
); |
|
|
|
|
|
orbit.electrons.forEach((electron) { |
|
|
electron.currentSize = lerpDouble(electron.currentSize, electron.targetSize, 0.08); |
|
|
electron.currentColor = Color.lerp(electron.currentColor, electron.targetColor, 0.08); |
|
|
final degree = math.pi * 2 * electron.positionPercent; |
|
|
canvas.drawCircle( |
|
|
center + Offset(math.cos(degree) * (width / 2), math.sin(degree) * (height / 2)), |
|
|
electron.currentSize / 2, |
|
|
Paint() |
|
|
..color = electron.currentColor |
|
|
..style = PaintingStyle.fill |
|
|
..strokeCap = StrokeCap.round |
|
|
..strokeWidth = electron.currentSize, |
|
|
); |
|
|
}); |
|
|
} |
|
|
|
|
|
@override |
|
|
bool shouldRepaint(covariant _OrbitPainter oldDelegate) => true; |
|
|
} |