【Flutter 实战】自定义动画-涟漪和雷达扫描

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

此动画通过 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),
);
},
);
}
}
count 和 color 分别代表水波纹的数量和颜色。
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 方法,根据动画进度计算颜色的透明度和半径。
使用如下:
class WaterRipplePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(height: 200, width: 200, child: WaterRipple())),
);
}
}
雷达扫描
实现雷达扫描效果:

此效果分为两部分:中间的 logo 图片和扫描部分。
中间的 logo 图片
中间的 logo 图片边缘有阴影效果,像是太阳发光一样,实现:
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博客地址(330个控件用法):http://laomengit.com
欢迎加入Flutter交流群(微信:laomengit)、关注公众号【老孟Flutter】:
![]() |
![]() |
【Flutter 实战】自定义动画-涟漪和雷达扫描的更多相关文章
- 【Flutter 实战】动画核心
老孟导读:动画系统是任何一个UI框架的核心功能,也是开发者学习一个UI框架的重中之重,同时也是比较难掌握的一部分,下面我们就一层一层的揭开 Flutter 动画的面纱. 任何程序的动画原理都是一样的, ...
- 【Flutter 实战】动画序列、共享动画、路由动画
老孟导读:此篇文章是 Flutter 动画系列文章第四篇,本文介绍动画序列.共享动画.路由动画. 动画序列 Flutter中组合动画使用Interval,Interval继承自Curve,用法如下: ...
- 【Flutter 实战】17篇动画系列文章带你走进自定义动画
老孟导读:Flutter 动画系列文章分为三部分:基础原理和核心概念.系统动画组件.8篇自定义动画案例,共17篇. 动画核心概念 在开发App的过程中,自定义动画必不可少,Flutter 中想要自定义 ...
- 【Flutter实战】自定义滚动条
老孟导读:[Flutter实战]系列文章地址:http://laomengit.com/guide/introduction/mobile_system.html 默认情况下,Flutter 的滚动组 ...
- 【Flutter 实战】一文学会20多个动画组件
老孟导读:此篇文章是 Flutter 动画系列文章第三篇,后续还有动画序列.过度动画.转场动画.自定义动画等. Flutter 系统提供了20多个动画组件,只要你把前面[动画核心](文末有链接)的文章 ...
- 《Flutter实战》开源电子书
<Flutter实战>开源电子书 <Flutter实战> 开源了,本书为 Flutter中文网开源电子书项目,本书系统介绍了Flutter技术的各个方面,本书属于原创书籍(并非 ...
- Android 5.0自定义动画
材料设计中的动画对用户的操作给予了反馈,并且在与应用交互时提供了持续的可见性.材料主题提供了一些按钮动画和活动过渡,Android 5.0允许你自定义动画并且可以创建新的动画: Touch Feedb ...
- python全栈开发day48-jqurey自定义动画,jQuery属性操作,jQuery的文档操作,jQuery中的ajax
一.昨日内容回顾 1.jQuery初识 1).使用jQuery而非JS的六大理由 2).jQuery对象和js对象转换 3).jQuery的两大特点 4).jQuery的入口函数三大写法 5).jQu ...
- Flutter实战】文本组件及五大案例
老孟导读:大家好,这是[Flutter实战]系列文章的第二篇,这一篇讲解文本组件,文本组件包括文本展示组件(Text和RichText)和文本输入组件(TextField),基础用法和五个案例助你快速 ...
随机推荐
- PHP min() 函数
实例 通过 min() 函数查找最小值: <?php高佣联盟 www.cgewang.comecho(min(2,4,6,8,10) . "<br>");echo ...
- 7.18 NOI模拟赛 树论 线段树 树链剖分 树的直径的中心 SG函数 换根
LINK:树论 不愧是我认识的出题人 出的题就是牛掰 == 他好像不认识我 考试的时候 只会写42 还有两个subtask写挂了 拿了37 确实两个subtask合起来只有5分的好成绩 父亲能转移到自 ...
- SSRS - 请求因 HTTP 状态 401 失败: Unauthorized。
原因: 1.SSRS报表服务器停止了,重启就可以了 2.用户没有权限 3.用户登录密码过期了,重设密码就可以了(如果用户是安装了client的话,直接让他远程登录改一下密码)
- react ts redux-saga | 谷歌Chrome浏览器风格的标签组件 | 中台
谷歌Chrome浏览器风格的标签组件 选用技术 react typescript redux-saga存储本地标签数据 umi 实现 [x] 支持全部关闭,当前关闭,关闭其他Tab [x] 支持Tab ...
- property补充
property补充 # class Foo: # @property # def AAA(self): # print('get的时候运行我啊') # # @AAA.setter # def AAA ...
- elasticsearch 高级搜索示例 es7.0
基础数据 创建索引 PUT mytest { "mappings": { "properties": { "title": { " ...
- .Net 异常最佳做法
异常信息原因 异常是易于滥用的那些构造之一.这可能包括不应该在应有的情况下引发异常或在没有充分理由的情况下捕获异常.还有一个引发错误异常的问题,它不仅无助于我们,而且会使我们困惑.另一方面,存在正确处 ...
- JavaScript小游戏实例:简单的键盘练习
键盘是一种常用的输入设备,灵活熟练地使用键盘进行输入是计算机用户需掌握的一门基本功.下面我们编写一个简单的键盘练习游戏. 1.刺破气泡交互式小动画 在编写简单的键盘练习游戏之前,先设计一个简单地刺破气 ...
- Java进阶知识
01[Object类.常用API] 主要内容 Object类 Date类 DateFormat类 Calendar类 System类 StringBuilder类 包装类 第一章 Object类 1. ...
- 方法解析之Method与ConstMethod介绍
HotSpot通过Method与ConstMethod来保存方法元信息. 1.Method Method没有子类,定义在method.hpp文件中,其类继承关系如下图: Method用于表示一个Jav ...

