本文所有源码见
github/flutter_journey
1.何为动画
1.1:动画说明
见字如面,会动的画面。画面连续渲染,当速度快到一定程度,大脑就会呈现动感
1).何为运动:视觉上看是一个物体在不同的时间轴上表现出不同的物理位置 2).位移 = 初位移 + 速度 * 时间 小学生的知识不多说 3).速度 = 初速度 + 加速度 * 时间 初中生的知识不多说 4).时间、位移、速度、加速度构成了现代科学的运动体系
1.2:关于FPS
那刷新要有多快呢?不知你是否听过FPS,对就是那个游戏里很重要的FPS
FPS : Frames Per Second 画面每秒传输帧数(新率) 单位赫兹(Hz) 60Hz的刷新率刷也就是指屏幕一秒内刷新60次,即60帧/秒 其中常见的电影24fps,也就是一秒钟刷新24次。 要达到流畅,需要60fps,这也是游戏中的一个指标,否则就会感觉不流畅 一秒钟刷新60次,即16.66667ms刷新一次,这也是一个常见的值
1.3:代码中的动画
可以用代码模拟运动,不断刷新的同时改变运动物体的属性从而形成动画
在Android中有ValueAnimator
,JavaScript(浏览器)中有``.
1.时间:无限执行----模拟时间流,每次刷新时间间隔,记为:1T 2.位移:物体在屏幕像素位置----模拟世界,每个像素距离记为:1px 3.速度(单位px/T)、加速度(px/T^2) 注意:无论什么语言,只要能够模拟时间与位移,本篇的思想都可以适用,只是语法不同罢了
2.粒子动画
2.1:Flutter中的时间流
通过AnimationController来实现一个不断刷新的舞台,那么表演就交给你了
class RunBall extends StatefulWidget { @override _RunBallState createState() => _RunBallState(); } class _RunBallState extends State<RunBall> with SingleTickerProviderStateMixin { AnimationController controller; var _oldTime = DateTime.now().millisecondsSinceEpoch;//首次运行时时间 @override Widget build(BuildContext context) { var child = Scaffold( ); return GestureDetector(//手势组件,做点击响应 child: child, onTap: () { controller.forward();//执行动画 }, ); } @override void initState() { controller =//创建AnimationController对象 AnimationController(duration: Duration(days: 999 * 365), vsync: this); controller.addListener(() {//添加监听,执行渲染 _render(); }); } @override void dispose() { controller.dispose(); // 资源释放 } //渲染方法,更新状态 _render() { setState(() { var now = DateTime.now().millisecondsSinceEpoch;//每一刷新时间 print("时间差:${now - _oldTime}ms");//打印时间差 _oldTime = now;//重新赋值 }); } }
2.2:静态小球的绘制
又到了我们的Canvas了
///小球信息描述类 class Ball { double aX; //加速度 double aY; //加速度Y double vX; //速度X double vY; //速度Y double x; //点位X double y; //点位Y Color color; //颜色 double r;//小球半径 Ball({this.x=0, this.y=0, this.color, this.r=10, this.aX=0, this.aY=0, this.vX=0, this.vY=0}); } ///画板Painter class RunBallView extends CustomPainter { Ball _ball; //小球 Rect _area;//运动区域 Paint mPaint; //主画笔 Paint bgPaint; //背景画笔 RunBallView(this._ball,this._area) { mPaint = new Paint(); bgPaint = new Paint()..color = Color.fromARGB(148, 198, 246, 248); } @override void paint(Canvas canvas, Size size) { canvas.drawRect(_area, bgPaint); _drawBall(canvas, _ball); } @override bool shouldRepaint(CustomPainter oldDelegate) { return true; } ///使用[canvas] 绘制某个[ball] void _drawBall(Canvas canvas, Ball ball) { canvas.drawCircle( Offset(ball.x, ball.y), ball.r, mPaint..color = ball.color); } } var _area= Rect.fromLTRB(0+40.0,0+200.0,280+40.0,200+200.0); var _ball = Ball(color: Colors.blueAccent, r: 10,x: 40.0+140,y:200.0+100); ---->[使用:_RunBallState#build]---- var child = Scaffold( body: CustomPaint( painter: RunBallView(_ball,_area), ), );
2.3:远动盒
也就是控制小球在每次刷新时改变其属性,这样视觉上就是运动状态
在边界碰撞后,改变方向即可,通过下面三步,一个运动盒就完成了
//[1].为小球附上初始速度和加速度 var _ball = Ball(color: Colors.blueAccent, r: 10,aY: 0.1, vX: 2, vY: -2,x: 40.0+140,y:200.0+100); //[2].核心渲染方法,每次调用时更新小球信息 _render() { updateBall(); setState(() { var now = DateTime.now().millisecondsSinceEpoch; print("时间差:${now - _oldTime}ms,帧率:${1000/(now - _oldTime)}"); _oldTime = now; }); } //[3].更新小球的信息 void updateBall() { //运动学公式 _ball.x += _ball.vX; _ball.y += _ball.vY; _ball.vX += _ball.aX; _ball.vY += _ball.aY; //限定下边界 if (_ball.y > _area.bottom - _ball.r) { _ball.y = _area.bottom - _ball.r; _ball.vY = -_ball.vY; _ball.color=randomRGB();//碰撞后随机色 } //限定上边界 if (_ball.y < _area.top + _ball.r) { _ball.y = _area.top + _ball.r; _ball.vY = -_ball.vY; _ball.color=randomRGB();//碰撞后随机色 } //限定左边界 if (_ball.x < _area.left + _ball.r) { _ball.x = _area.left + _ball.r; _ball.vX = -_ball.vX; _ball.color=randomRGB();//碰撞后随机色 } //限定右边界 if (_ball.x > _area.right - _ball.r) { _ball.x = _area.right - _ball.r; _ball.vX= -_ball.vX; _ball.color=randomRGB();//碰撞后随机色 } } }
2.4:让小球按照指定的函数图像运动
给定一个较小的dx,随着dx增加,根据函数求出dy,然后更新小球信息
如下面的sin图像,随着每次更新,根据函数关系约束小球坐标值
double dx=0.0; void updateBall(){ dx+=pi/180;//每次dx增加pi/180 _ball.x+=dx; _ball.y+=f(dx); } f(x){ var y= 5*sin(4*x);//函数表达式 return y; }
或者让小球按圆形轨迹运动,下面是通过参数方程让呈圆形轨迹
也就是数学学得好,想怎么跑怎么跑。
double dx=0.0; void updateBall(){ dx+=pi/180;//每次dx增加pi/180 _ball.x+=cos(dx); _ball.y+=sin(dx); }
3.粒子束
3.1:多个粒子运动
一个粒子运动已经够好玩的,那么许多粒子会怎么样?
需要改变的是RunBallView的入参,由一个球换成小球列表,
绘画时批量绘制,更新信息时批量更新
//[1].单体改成列表 class RunBallView extends CustomPainter { List<Ball> _balls; //小球列表 //[2].绘画时批量绘制 void paint(Canvas canvas, Size size) { _balls.forEach((ball) { _drawBall(canvas, ball); }); } //[3].渲染时批量更改信息 _render() { for (var i = 0; i < _balls.length; i++) { updateBall(i); } setState(() { }); } //[4]._RunBallState中初始化时生成随机信息的小球 for (var i = 0; i < 30; i++) { _balls.add(Ball( color: randomRGB(), r: 5 + 4 * random.nextDouble(), vX: 3*random.nextDouble()*pow(-1, random.nextInt(20)), vY: 3*random.nextDouble()*pow(-1, random.nextInt(20)), aY: 0.1, x: 200, y: 300)); }
也许你觉得画小球没什么,但要知道,小球只是单体,
你可以换成任意你能绘制的东西,甚至是图片或组件
3.2:撞击分裂的效果
也就是在恰当的时机可以添加粒子而达到一定的视觉效果
核心是当到达边界后进行处理,将原来的粒子半径减半,再添加一个等大反向的粒子
//限定下边界 if (ball.y > _area.bottom) { var newBall = Ball.fromBall(ball); newBall.r = newBall.r / 2; newBall.vX = -newBall.vX; newBall.vY = -newBall.vY; _balls.add(newBall); ball.r = ball.r / 2; ball.y = _area.bottom; ball.vY = -ball.vY; ball.color = randomRGB(); //碰撞后随机色 }
当越分越多时,会存在大量绘制,这时可以控制一下条件来移除
void updateBall(int i) { var ball = _balls[i]; if (ball.r < 0.3) { //半径小于0.3就移除 _balls.removeAt(i); } //略... }
3.3:特定粒子
现在可以感受到,动画就是元素的信息在不断变化,给人产生的感觉
只要将信息描述好,那么你可以完成任何动画,你就是创造者与主宰者
* 渲染数字
* @param num 要显示的数字
* @param canvas 画布
*/
void renderDigit(double radius) {
var one = [
[0, 0, 0, 1, 1, 0, 0],
[0, 1, 1, 1, 1, 0, 0],
[0, 0, 0, 1, 1, 0, 0],
[0, 0, 0, 1, 1, 0, 0],
[0, 0, 0, 1, 1, 0, 0],
[0, 0, 0, 1, 1, 0, 0],
[0, 0, 0, 1, 1, 0, 0],
[0, 0, 0, 1, 1, 0, 0],
[0, 0, 0, 1, 1, 0, 0],
[1, 1, 1, 1, 1, 1, 1]
]; //1
for (int i = 0; i < one.length; i++) {
for (int j = 0; j < one[j].length; j++) {
if (one[i][j] == 1) {
double rX = j * 2 * (radius + 1) + (radius + 1); //第(i,j)个点圆心横坐标
double rY = i * 2 * (radius + 1) + (radius + 1); //第(i,j)个点圆心纵坐标
_balls.add(Ball(
r: radius,
x: rX,
y: rY,
color: randomRGB(),
vX: 3 * random.nextDouble() * pow(-1, random.nextInt(20)),
vY: 3 * random.nextDouble() * pow(-1, random.nextInt(20))));
}
}
}
}
然后通过信息创建小球,通过渲染展现出来,通过动画将其运动。
其实通过像素点也可以记录这些信息,就可以将图片进行粒子画,
之前在Android粒子篇之Bitmap像素级操作 写得很信息,这里不展开了
总的来说,动画包括三个重要的条件
时间流,渲染绘制,信息更新逻辑
这并不只是对于Flutter,任何语言只要满足这三点,粒子动画就可以跑起来
至于有什么用,也许可以提醒我,我不是搬砖的,而是程序设计师一个Creater...
共同学习,写下你的评论
评论加载中...
作者其他优质文章