老孟導讀:此篇文章是 Flutter 動畫系列文章第五篇,本文介紹2個自定義動畫: 漣漪和 雷達掃描效果。
實現漣漪動畫效果以下:git
此動畫經過 CustomPainter 繪製配合 AnimationController 動畫控制實現,定義動畫控制部分:canvas
class WaterRipple extends StatefulWidget { final int count; final Color color; const WaterRipple({Key key, this.count = 3, this.color = const Color(0xFF0080ff)}) : super(key: key); @override _WaterRippleState createState() => _WaterRippleState(); } class _WaterRippleState extends State<WaterRipple> with SingleTickerProviderStateMixin { AnimationController _controller; @override void initState() { _controller = AnimationController(vsync: this, duration: Duration(milliseconds: 2000)) ..repeat(); super.initState(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _controller, builder: (context, child) { return CustomPaint( painter: WaterRipplePainter(_controller.value,count: widget.count,color: widget.color), ); }, ); } }
count 和 color 分別表明水波紋的數量和顏色。微信
WaterRipplePainter 定義以下:less
class WaterRipplePainter extends CustomPainter { final double progress; final int count; final Color color; Paint _paint = Paint()..style = PaintingStyle.fill; WaterRipplePainter(this.progress, {this.count = 3, this.color = const Color(0xFF0080ff)}); @override void paint(Canvas canvas, Size size) { double radius = min(size.width / 2, size.height / 2); for (int i = count; i >= 0; i--) { final double opacity = (1.0 - ((i + progress) / (count + 1))); final Color _color = color.withOpacity(opacity); _paint..color = _color; double _radius = radius * ((i + progress) / (count + 1)); canvas.drawCircle( Offset(size.width / 2, size.height / 2), _radius, _paint); } } @override bool shouldRepaint(CustomPainter oldDelegate) { return true; } }
重點是 paint 方法,根據動畫進度計算顏色的透明度和半徑。ide
使用以下:動畫
class WaterRipplePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Container(height: 200, width: 200, child: WaterRipple())), ); } }
實現雷達掃描效果:ui
此效果分爲兩部分:中間的 logo 圖片和掃描部分。this
中間的 logo 圖片邊緣有陰影效果,像是太陽發光同樣,實現:spa
Container( height: 70.0, width: 70.0, decoration: BoxDecoration( color: Colors.grey, image: DecorationImage( image: AssetImage('assets/images/logo.png')), shape: BoxShape.circle, boxShadow: [ BoxShadow( color: Colors.white.withOpacity(.5), blurRadius: 5.0, spreadRadius: 3.0, ), ]), )
定義雷達掃描的動畫控制器:rest
class RadarView extends StatefulWidget { @override _RadarViewState createState() => _RadarViewState(); } class _RadarViewState extends State<RadarView> with SingleTickerProviderStateMixin { AnimationController _controller; Animation<double> _animation; @override void initState() { _controller = AnimationController(vsync: this, duration: Duration(seconds: 5)); _animation = Tween(begin: .0, end: pi * 2).animate(_controller); _controller.repeat(); super.initState(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _animation, builder: (context, child) { return CustomPaint( painter: RadarPainter(_animation.value), ); }, ); } }
RadarPainter 定義以下:
class RadarPainter extends CustomPainter { final double angle; Paint _bgPaint = Paint() ..color = Colors.white ..strokeWidth = 1 ..style = PaintingStyle.stroke; Paint _paint = Paint()..style = PaintingStyle.fill; int circleCount = 3; RadarPainter(this.angle); @override void paint(Canvas canvas, Size size) { var radius = min(size.width / 2, size.height / 2); canvas.drawLine(Offset(size.width / 2, size.height / 2 - radius), Offset(size.width / 2, size.height / 2 + radius), _bgPaint); canvas.drawLine(Offset(size.width / 2 - radius, size.height / 2), Offset(size.width / 2 + radius, size.height / 2), _bgPaint); for (var i = 1; i <= circleCount; ++i) { canvas.drawCircle(Offset(size.width / 2, size.height / 2), radius * i / circleCount, _bgPaint); } _paint.shader = ui.Gradient.sweep( Offset(size.width / 2, size.height / 2), [Colors.white.withOpacity(.01), Colors.white.withOpacity(.5)], [.0, 1.0], TileMode.clamp, .0, pi / 12); canvas.save(); double r = sqrt(pow(size.width, 2) + pow(size.height, 2)); double startAngle = atan(size.height / size.width); Point p0 = Point(r * cos(startAngle), r * sin(startAngle)); Point px = Point(r * cos(angle + startAngle), r * sin(angle + startAngle)); canvas.translate((p0.x - px.x) / 2, (p0.y - px.y) / 2); canvas.rotate(angle); canvas.drawArc( Rect.fromCircle( center: Offset(size.width / 2, size.height / 2), radius: radius), 0, pi / 12, true, _paint); canvas.restore(); } @override bool shouldRepaint(CustomPainter oldDelegate) { return true; } }
將二者結合在一塊兒:
class RadarPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Color(0xFF0F1532), body: Stack( children: [ Positioned.fill( left: 10, right: 10, child: Center( child: Stack(children: [ Positioned.fill( child: RadarView(), ), Positioned( child: Center( child: Container( height: 70.0, width: 70.0, decoration: BoxDecoration( color: Colors.grey, image: DecorationImage( image: AssetImage('assets/images/logo.png')), shape: BoxShape.circle, boxShadow: [ BoxShadow( color: Colors.white.withOpacity(.5), blurRadius: 5.0, spreadRadius: 3.0, ), ]), ), ), ), ]), ), ) ], )); } }
老孟Flutter博客地址(330個控件用法):http://laomengit.com
歡迎加入Flutter交流羣(微信:laomengit)、關注公衆號【老孟Flutter】: