【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),基础用法和五个案例助你快速 ...
随机推荐
- java -jar .jar中没有主清单属性
pom里加上 <build> <plugins> <plugin> <groupId>org.springframework.boot</grou ...
- react-router分析 - 一、history
react-router基于history库,它是一个管理js应用session会话历史的js库.它将不同环境(浏览器,node等)的变量统一成了一个简易的API来管理历史堆栈.导航.确认跳转.以及s ...
- 【leetcode每日两题】-Day1-简单题
1. 两数之和 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标. 你可以假设每种输入只会对应一个答案.但是,数组中同一个元素 ...
- 同事问我MySQL怎么递归查询,我懵逼了
前言 最近在做的业务场景涉及到了数据库的递归查询.我们公司用的 Oracle ,众所周知,Oracle 自带有递归查询的功能,所以实现起来特别简单. 但是,我记得 MySQL 是没有递归查询功能的,那 ...
- .net core编写转发服务
我有个小伙伴问我,他需要写一个转发服务的他有很多功能要通过他的服务转发~ 技术栈又不一定asp.net core,我就想起泥水老前辈的BeetleX.FastHttpApi 中午午休,折腾了一会儿前辈 ...
- 29-main()的使用说明
* 1. main()方法作为程序的入口 * 2. main()方法也是一个普通的静态方法 * 3. main()方法可以作为我们与控制台交互的方式.(使用Scanner) 如何将控制台获取的数据传给 ...
- VSCode C++环境配置(个人使用)
tasks.json { "version": "2.0.0", "command": "g++", "arg ...
- 嵌入式linux简介
嵌入式linux系统应用非常广泛,涵盖各行各业,基于ARM.mips等微处理器架构的硬件平台.基于嵌入式linux系统的设备已经深入生活中各个角落,随处可见. 我们常说的嵌入式linux系统,其实 ...
- 001_HyperLedger Fabric环境安装
HyperLedger Fabric的环境,有解决三大问题 第一,是系统环境,这里我们选择的是centos7 第二,是开发环境,这里我们选择的是Go语言 第三,是运行环境,这里我们选择的是Docker ...
- 节点操作 - DOM编程
1. 获取节点 1.1 直接获取节点 父子关系: element.parentNode element.firstChild/element.lastChild element.firstElemen ...