老孟导读:此篇文章是 Flutter 动画系列文章第五篇,本文介绍2个自定义动画:涟漪雷达扫描效果。

涟漪

实现涟漪动画效果如下:

此动画通过 CustomPainter 绘制配合 AnimationController 动画控制实现,定义动画控制部分:

  1. class WaterRipple extends StatefulWidget {
  2. final int count;
  3. final Color color;
  4. const WaterRipple({Key key, this.count = 3, this.color = const Color(0xFF0080ff)}) : super(key: key);
  5. @override
  6. _WaterRippleState createState() => _WaterRippleState();
  7. }
  8. class _WaterRippleState extends State<WaterRipple>
  9. with SingleTickerProviderStateMixin {
  10. AnimationController _controller;
  11. @override
  12. void initState() {
  13. _controller =
  14. AnimationController(vsync: this, duration: Duration(milliseconds: 2000))
  15. ..repeat();
  16. super.initState();
  17. }
  18. @override
  19. void dispose() {
  20. _controller.dispose();
  21. super.dispose();
  22. }
  23. @override
  24. Widget build(BuildContext context) {
  25. return AnimatedBuilder(
  26. animation: _controller,
  27. builder: (context, child) {
  28. return CustomPaint(
  29. painter: WaterRipplePainter(_controller.value,count: widget.count,color: widget.color),
  30. );
  31. },
  32. );
  33. }
  34. }

countcolor 分别代表水波纹的数量和颜色。

WaterRipplePainter 定义如下:

  1. class WaterRipplePainter extends CustomPainter {
  2. final double progress;
  3. final int count;
  4. final Color color;
  5. Paint _paint = Paint()..style = PaintingStyle.fill;
  6. WaterRipplePainter(this.progress,
  7. {this.count = 3, this.color = const Color(0xFF0080ff)});
  8. @override
  9. void paint(Canvas canvas, Size size) {
  10. double radius = min(size.width / 2, size.height / 2);
  11. for (int i = count; i >= 0; i--) {
  12. final double opacity = (1.0 - ((i + progress) / (count + 1)));
  13. final Color _color = color.withOpacity(opacity);
  14. _paint..color = _color;
  15. double _radius = radius * ((i + progress) / (count + 1));
  16. canvas.drawCircle(
  17. Offset(size.width / 2, size.height / 2), _radius, _paint);
  18. }
  19. }
  20. @override
  21. bool shouldRepaint(CustomPainter oldDelegate) {
  22. return true;
  23. }
  24. }

重点是 paint 方法,根据动画进度计算颜色的透明度和半径。

使用如下:

  1. class WaterRipplePage extends StatelessWidget {
  2. @override
  3. Widget build(BuildContext context) {
  4. return Scaffold(
  5. body: Center(
  6. child: Container(height: 200, width: 200, child: WaterRipple())),
  7. );
  8. }
  9. }

雷达扫描

实现雷达扫描效果:

此效果分为两部分:中间的 logo 图片和扫描部分。

中间的 logo 图片

中间的 logo 图片边缘有阴影效果,像是太阳发光一样,实现:

  1. Container(
  2. height: 70.0,
  3. width: 70.0,
  4. decoration: BoxDecoration(
  5. color: Colors.grey,
  6. image: DecorationImage(
  7. image: AssetImage('assets/images/logo.png')),
  8. shape: BoxShape.circle,
  9. boxShadow: [
  10. BoxShadow(
  11. color: Colors.white.withOpacity(.5),
  12. blurRadius: 5.0,
  13. spreadRadius: 3.0,
  14. ),
  15. ]),
  16. )

扫描

定义雷达扫描的动画控制器:

  1. class RadarView extends StatefulWidget {
  2. @override
  3. _RadarViewState createState() => _RadarViewState();
  4. }
  5. class _RadarViewState extends State<RadarView>
  6. with SingleTickerProviderStateMixin {
  7. AnimationController _controller;
  8. Animation<double> _animation;
  9. @override
  10. void initState() {
  11. _controller =
  12. AnimationController(vsync: this, duration: Duration(seconds: 5));
  13. _animation = Tween(begin: .0, end: pi * 2).animate(_controller);
  14. _controller.repeat();
  15. super.initState();
  16. }
  17. @override
  18. void dispose() {
  19. _controller.dispose();
  20. super.dispose();
  21. }
  22. @override
  23. Widget build(BuildContext context) {
  24. return AnimatedBuilder(
  25. animation: _animation,
  26. builder: (context, child) {
  27. return CustomPaint(
  28. painter: RadarPainter(_animation.value),
  29. );
  30. },
  31. );
  32. }
  33. }

RadarPainter 定义如下:

  1. class RadarPainter extends CustomPainter {
  2. final double angle;
  3. Paint _bgPaint = Paint()
  4. ..color = Colors.white
  5. ..strokeWidth = 1
  6. ..style = PaintingStyle.stroke;
  7. Paint _paint = Paint()..style = PaintingStyle.fill;
  8. int circleCount = 3;
  9. RadarPainter(this.angle);
  10. @override
  11. void paint(Canvas canvas, Size size) {
  12. var radius = min(size.width / 2, size.height / 2);
  13. canvas.drawLine(Offset(size.width / 2, size.height / 2 - radius),
  14. Offset(size.width / 2, size.height / 2 + radius), _bgPaint);
  15. canvas.drawLine(Offset(size.width / 2 - radius, size.height / 2),
  16. Offset(size.width / 2 + radius, size.height / 2), _bgPaint);
  17. for (var i = 1; i <= circleCount; ++i) {
  18. canvas.drawCircle(Offset(size.width / 2, size.height / 2),
  19. radius * i / circleCount, _bgPaint);
  20. }
  21. _paint.shader = ui.Gradient.sweep(
  22. Offset(size.width / 2, size.height / 2),
  23. [Colors.white.withOpacity(.01), Colors.white.withOpacity(.5)],
  24. [.0, 1.0],
  25. TileMode.clamp,
  26. .0,
  27. pi / 12);
  28. canvas.save();
  29. double r = sqrt(pow(size.width, 2) + pow(size.height, 2));
  30. double startAngle = atan(size.height / size.width);
  31. Point p0 = Point(r * cos(startAngle), r * sin(startAngle));
  32. Point px = Point(r * cos(angle + startAngle), r * sin(angle + startAngle));
  33. canvas.translate((p0.x - px.x) / 2, (p0.y - px.y) / 2);
  34. canvas.rotate(angle);
  35. canvas.drawArc(
  36. Rect.fromCircle(
  37. center: Offset(size.width / 2, size.height / 2), radius: radius),
  38. 0,
  39. pi / 12,
  40. true,
  41. _paint);
  42. canvas.restore();
  43. }
  44. @override
  45. bool shouldRepaint(CustomPainter oldDelegate) {
  46. return true;
  47. }
  48. }

将两者结合在一起:

  1. class RadarPage extends StatelessWidget {
  2. @override
  3. Widget build(BuildContext context) {
  4. return Scaffold(
  5. backgroundColor: Color(0xFF0F1532),
  6. body: Stack(
  7. children: [
  8. Positioned.fill(
  9. left: 10,
  10. right: 10,
  11. child: Center(
  12. child: Stack(children: [
  13. Positioned.fill(
  14. child: RadarView(),
  15. ),
  16. Positioned(
  17. child: Center(
  18. child: Container(
  19. height: 70.0,
  20. width: 70.0,
  21. decoration: BoxDecoration(
  22. color: Colors.grey,
  23. image: DecorationImage(
  24. image: AssetImage('assets/images/logo.png')),
  25. shape: BoxShape.circle,
  26. boxShadow: [
  27. BoxShadow(
  28. color: Colors.white.withOpacity(.5),
  29. blurRadius: 5.0,
  30. spreadRadius: 3.0,
  31. ),
  32. ]),
  33. ),
  34. ),
  35. ),
  36. ]),
  37. ),
  38. )
  39. ],
  40. ));
  41. }
  42. }

交流

老孟Flutter博客地址(330个控件用法):http://laomengit.com

欢迎加入Flutter交流群(微信:laomengit)、关注公众号【老孟Flutter】:

【Flutter 实战】自定义动画-涟漪和雷达扫描的更多相关文章

  1. 【Flutter 实战】动画核心

    老孟导读:动画系统是任何一个UI框架的核心功能,也是开发者学习一个UI框架的重中之重,同时也是比较难掌握的一部分,下面我们就一层一层的揭开 Flutter 动画的面纱. 任何程序的动画原理都是一样的, ...

  2. 【Flutter 实战】动画序列、共享动画、路由动画

    老孟导读:此篇文章是 Flutter 动画系列文章第四篇,本文介绍动画序列.共享动画.路由动画. 动画序列 Flutter中组合动画使用Interval,Interval继承自Curve,用法如下: ...

  3. 【Flutter 实战】17篇动画系列文章带你走进自定义动画

    老孟导读:Flutter 动画系列文章分为三部分:基础原理和核心概念.系统动画组件.8篇自定义动画案例,共17篇. 动画核心概念 在开发App的过程中,自定义动画必不可少,Flutter 中想要自定义 ...

  4. 【Flutter实战】自定义滚动条

    老孟导读:[Flutter实战]系列文章地址:http://laomengit.com/guide/introduction/mobile_system.html 默认情况下,Flutter 的滚动组 ...

  5. 【Flutter 实战】一文学会20多个动画组件

    老孟导读:此篇文章是 Flutter 动画系列文章第三篇,后续还有动画序列.过度动画.转场动画.自定义动画等. Flutter 系统提供了20多个动画组件,只要你把前面[动画核心](文末有链接)的文章 ...

  6. 《Flutter实战》开源电子书

    <Flutter实战>开源电子书 <Flutter实战> 开源了,本书为 Flutter中文网开源电子书项目,本书系统介绍了Flutter技术的各个方面,本书属于原创书籍(并非 ...

  7. Android 5.0自定义动画

    材料设计中的动画对用户的操作给予了反馈,并且在与应用交互时提供了持续的可见性.材料主题提供了一些按钮动画和活动过渡,Android 5.0允许你自定义动画并且可以创建新的动画: Touch Feedb ...

  8. python全栈开发day48-jqurey自定义动画,jQuery属性操作,jQuery的文档操作,jQuery中的ajax

    一.昨日内容回顾 1.jQuery初识 1).使用jQuery而非JS的六大理由 2).jQuery对象和js对象转换 3).jQuery的两大特点 4).jQuery的入口函数三大写法 5).jQu ...

  9. Flutter实战】文本组件及五大案例

    老孟导读:大家好,这是[Flutter实战]系列文章的第二篇,这一篇讲解文本组件,文本组件包括文本展示组件(Text和RichText)和文本输入组件(TextField),基础用法和五个案例助你快速 ...

随机推荐

  1. MacOS下Nginx安装

    1. 先安装homebrew 2. 安装Nginx,终端下执行: $ brew install nginx 安装过程中会自己安装依赖: 3. 启动nginx服务 $ nginx 成功后,使用浏览器打开 ...

  2. 牛客练习赛63 牛牛的树行棋 差分 树上博弈 sg函数

    LINK:牛牛的树行棋 本来是不打算写题解的. 不过具体思考 还是有一段时间的. 看完题 一直想转换到阶梯NIM的模型上 转换失败. 考虑SG函数. 容易发现 SG函数\(sg_x=max{sg_{t ...

  3. 4.11 省选模拟赛 序列 二分 线段树优化dp set优化dp 缩点

    容易想到二分. 看到第一个条件容易想到缩点. 第二个条件自然是分段 然后让总和最小 容易想到dp. 缩点为先:我是采用了取了一个前缀最小值数组 二分+并查集缩点 当然也是可以直接采用 其他的奇奇怪怪的 ...

  4. electron开发 - mac平台的打包和签名 - 针对electron-builder的自动更新

    配合自动更新 - 安装包签名(mac)(待补充) 前提:实现自动更新需要签名 一.本地开发调试mac签名(无需具有权限的appple账号) 打开xcode,xcode -> preference ...

  5. [转]Tomcat 调优的技巧

    原文地址:https://mp.weixin.qq.com/s/7_bz3OPoH3x7xkkwkhJhbw Tomcat调优这块,在面试中会经常问道.目前Tomcat调优主要分为2大类,当前解释用的 ...

  6. 笨办法学python3代码练习ex23.py 字符串字节串字符编码

    首先简单说一下字符编码的问题.平常遇到比较多的就是ASCII码(全称:美国信息交换标准码).ASCII码使用一个字节(8位)来表示一些常见的数字.英文字母以及一些控制字符.英语用128个符号编码就够了 ...

  7. 比PS还好用!Python 20行代码批量抠图

    你是否曾经想将某张照片中的人物抠出来,然后拼接到其他图片上去,从而可以即使你在天涯海角,我也可以到此一游? 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在 ...

  8. Python玩转各种多媒体,视频、音频到图片

    我们经常会遇到一些对于多媒体文件修改的操作,像是对视频文件的操作:视频剪辑.字幕编辑.分离音频.视频音频混流等.又比如对音频文件的操作:音频剪辑,音频格式转换.再比如我们最常用的图片文件,格式转换.各 ...

  9. 重写ThreadPoolTaskExecutor

    目录 主类开启异步注解 创建线程池配置类 创建线程池实现类 创建一个测试类Controller 创建异步Service方法 定义异步的实现类 ThreadPoolExecutor:JDK内置线程池实现 ...

  10. AutoUpdater迁移到Github

    一. 摘要 最近一两年在做跨平台的解决方案,使应用程序能支持Android, iOS, Windows, MacOs. Linux等操作系统,在Android, iOS上可以使用Google Play ...