
1.何为动画
1.1:动画说明
1).何为运动:视觉上看是一个物体在不同的时间轴上表现出不同的物理位置
2).位移 = 初位移 + 速度 * 时间 小学生的知识不多说
3).速度 = 初速度 + 加速度 * 时间 初中生的知识不多说
4).时间、位移、速度、加速度构成了现代科学的运动体系
1.2:关于FPS
FPS : Frames Per Second 画面每秒传输帧数(新率) 单位赫兹(Hz)
60Hz的刷新率刷也就是指屏幕一秒内刷新60次,即60帧/秒
其中常见的电影24fps,也就是一秒钟刷新24次。
要达到流畅,需要60fps,这也是游戏中的一个指标,否则就会感觉不流畅
一秒钟刷新60次,即16.66667ms刷新一次,这也是一个常见的值
1.3:代码中的动画
1.时间:无限执行----模拟时间流,每次刷新时间间隔,记为:1T
2.位移:物体在屏幕像素位置----模拟世界,每个像素距离记为:1px
3.速度(单位px/T)、加速度(px/T^2)
注意:无论什么语言,只要能够模拟时间与位移,本篇的思想都可以适用,只是语法不同罢了
2.粒子动画
2.1:Flutter中的时间流

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:静态小球的绘制

///小球信息描述类
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:让小球按照指定的函数图像运动
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:多个粒子运动
//[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))));
}
}
}
}
结语