【Flutter 實戰】自定義動畫-漣漪和雷達掃描


老孟導讀:此篇文章是 Flutter 動畫系列文章第五篇,本文介紹2個自定義動畫:漣漪雷達掃描效果。web

漣漪

實現漣漪動畫效果以下:canvas

此動畫經過 CustomPainter 繪製配合 AnimationController 動畫控制實現,定義動畫控制部分:微信

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), ); }, ); }}

countcolor 分別表明水波紋的數量和顏色。less

WaterRipplePainter 定義以下:編輯器

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

使用以下:flex

class WaterRipplePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Container(height: 200, width: 200, child: WaterRipple())), ); }}

雷達掃描

實現雷達掃描效果:動畫

此效果分爲兩部分:中間的 logo 圖片和掃描部分。ui

中間的 logo 圖片

中間的 logo 圖片邊緣有陰影效果,像是太陽發光同樣,實現:this

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, ), ]),)

掃描

定義雷達掃描的動畫控制器:

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」
讓你天天進步一點點




本文分享自微信公衆號 - 老孟Flutter(lao_meng_qd)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索