Flutter学习第二天----2018-12-17----天气晴朗
零前言:
昨天讲了一下Flutter的基础项目,介绍了一下Dart语言,简单的用Canvas绘个图
本来打算今天把内置控件看一下的,不过既然昨天把图都画了,今天不玩动画岂不可惜
今天主要是把动画理一下,顺便把Android-java粒子运动复刻到Flutter-Dart里
最后会实现一个粒子时钟,Java版详见:Android原生绘图之炫酷倒计时
所以这篇会挺好玩的,瓜子,饮料,花生米备好,开始看吧
先挑几个图镇楼吧
时钟运动盒 | 粉碎球 |
---|---|
星与芒 | 星扩动 |
---|---|
一、入门级动画:五角星的长大
五角星的长大.gif
1.照葫芦画瓢
按照昨天的Flutter初始项目,我们来自己写一个
本人是喜欢分包的,Javaer的优良习惯。至少逻辑清晰,分工明确,我创建了一个pager包
主页面内容用AnimaPage,虽然暂时还不知道StatefulWidget是什么,反正按照套路出牌就行了
仿照初始项目的套路写,这里绘图区自定义AnimaView,打算对五角星的外接圆半径R进行动画
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:toly/view/anima_view.dart';
class AnimaPage extends StatefulWidget {
@override
_AnimaPageState createState() => _AnimaPageState();
}
class _AnimaPageState extends State<AnimaPage>{
double _R = 25;//五角星的外接圆半径
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("张风捷特烈"),
),
body: CustomPaint(
painter: AnimaView(context, _R),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
2.AnimaView的实现:
n角星的路径第一天已经封装好了,不会的可以去看一下
import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:toly/helper/common_path.dart';
import 'package:toly/helper/help_view.dart';
class AnimaView extends CustomPainter {
Paint mPaint;
BuildContext context;
double _R;
AnimaView(this.context, double r) {
mPaint = new Paint();
mPaint.color = Colors.deepOrange;
_R = r;
}
@override
void paint(Canvas canvas, Size size) {
var winSize = MediaQuery.of(context).size;
drawGrid(canvas, winSize);
drawCoo(canvas, new Size(160, 320), winSize);
canvas.translate(160, 320);
canvas.drawPath(nStarPath(5, _R, 50), mPaint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
3.让数据动起来
现在万事俱备,只欠东风把R的值吹动就行了,下面有请Animation登场
vsync需要with SingleTickerProviderStateMixi
Tween:补间动画----这里即:在 25.0, ~ 150.0之间在2000ms之内均匀变化
(PS:由于程序运行情况不同,并非绝对均匀,但整体上是均匀的)
..
:是级联运算,相当于再使用此对象,这里..
也就代表animation.
class AnimaPage extends StatefulWidget {
@override
_AnimaPageState createState() => _AnimaPageState();
}
class _AnimaPageState extends State<AnimaPage>
with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<double> animation;
double _R = 25;
@override
void initState() {
super.initState();
// 创建 AnimationController 对象
//|----vsync时会防止屏幕外动画(动画的UI不在当前屏幕时)消耗不必要的资源
controller = AnimationController(
duration: const Duration(milliseconds: 2000), vsync: this);
animation = Tween(begin: 25.0, end: 150.0).animate(controller)
..addListener(() {
setState(() {
_R = animation.value;
});
});
}
@override
void dispose() {
super.dispose();
controller.dispose(); // 资源释放
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("张风捷特烈"),
),
body: CustomPaint(
painter: AnimaView(context, _R),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
controller.forward(); //执行动画
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
这样最简单的动画就动起来了
4.来理一理思路
动画.png把源码翻一翻,感觉整个动画体系也不是非常复杂
套路就是:Animatable用animate方法,把一个Animation包裹一下,形成一个更厉害的Animation
至于他们的n个儿子,也就是对数据的处理不同,产生的效果不同罢了,套路知道了,一切好办
动画api.png可以看出api并没有想象中的那么多,所以别怕
二、入门级动画:五角星的绽放
前面用了补间动画Tween,而且只动了一下,下面带来连续运动的不均匀动画
匀速往复动 | 自定义曲线 | bounceInOut |
---|---|---|
1.往复运动
1.1:运动状态
你可以想象成一个人在一个范围数字跑道上跑步:
enum AnimationStatus {
/// The animation is stopped at the beginning
dismissed,//在正在开始时停止了
/// The animation is running from beginning to end
forward,//运动中
/// The animation is running backwards, from end to beginning
reverse,//跑到终点,再跑回来的时候
/// The animation is stopped at the end
completed,//跑到终点是
}
1.2:状态的监听
addStatusListener
:可以监听当前运动状态:只要让它跑完了,再往回跑就行了
animation = Tween(begin: 25.0, end: 150.0).animate(controller)
..addListener(() {
setState(() {
_R = animation.value;
});
})
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.reverse();
} else if (status == AnimationStatus.dismissed) {
controller.forward();
}
});
2.变速跑
就像运动员变速跑一样,感觉就像游戏里加buff,本来是匀速的Animation
给价格变速的buff就变速起来了,加buff的类就是Animatable
,它的子类有一个CurveTween
:
animation = Tween(begin: 25.0, end: 150.0).animate(
CurveTween(curve: Curves.bounceInOut).animate(controller))
bounceInOut.gif就这么简单,Curves里有几个内置的变速器,给原来的animation装上就行了
3.自定义变速曲线
3.1:追踪一下源码
---->[CurveTween]---------控件只有一参Curve------------------------
class CurveTween extends Animatable<double> {
/// Creates a curve tween.
///
/// The [curve] argument must not be null.
CurveTween({ @required this.curve })
: assert(curve != null);
/// The curve to use when transforming the value of the animation.
Curve curve;
---->[Curve]--------抽象的,找儿子去--------------------
@immutable
abstract class Curve {
---->[Curve]--------四参构造的曲线,整合我意--------------------
class Cubic extends Curve {
/// Creates a cubic curve.
///
/// Rather than creating a new instance, consider using one of the common
/// cubic curves in [Curves].
///
/// The [a], [b], [c], and [d] arguments must not be null.
const Cubic(this.a, this.b, this.c, this.d)
3.2:Chrome小工具
Cubic的获取.png Cubic.gif作为一名前端业余爱好者,Chrome里有个小东西很有用,
曲线生成,自带预览,简直无心插柳柳成荫。(记得掘金的头像可以转,有translate属性)
3.3:使用:
animation = Tween(begin: 25.0, end: 150.0).animate(
CurveTween(curve: Cubic(0.96, 0.13, 0.1, 1.2)).animate(controller))
自定义曲线.gif
Ok,基本上就这样,你get了吗?
三、初级动画:太阳的诞生
红太阳 | 星与阳 |
---|---|
1.红太阳:整型int 动画
套路学会了,这些动态改变一下n角星的尖角数,看看效果
1.1:AnimaPage里定义尖角数动画
Animation<int> numAnima;//n角星的尖角数动画
int _num = 5;//n角星的尖角数动画
controller = AnimationController(
duration: const Duration(milliseconds: 2000), vsync: this);
numAnima = IntTween(begin: 5, end: 220).animate(controller)
..addListener(() {
setState(() {
_num = numAnima.value;//设置属性,刷新界面
});
})
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.reverse();
} else if (status == AnimationStatus.dismissed) {
controller.forward();
}
});
1.2:AnimaView增加入参
int _num;
AnimaView(this.context, {double R, int num, Color color}) {
_num = num;
}
//绘制时使用_num即可
canvas.drawPath(nStarPath(_num, 100, 50), mPaint);
2.星与阳:颜色动画
ColorTween
相当于添加颜色改变的buff,入参的自己加吧,和上面一样,给画笔填色就行了
colorAnima =
ColorTween(begin: Colors.yellow, end: Colors.red).animate(controller)
..addListener(() {
setState(() {
_color = colorAnima.value;
});
})
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.reverse();
} else if (status == AnimationStatus.dismissed) {
controller.forward();
}
});
3.其他效果
自己玩玩吧,随便改些属性
星与芒 | 星扩动 |
---|---|
星与芒:固定五角星内接圆半径,外接圆半径变大,角数变多,颜色变红
星扩动:五角星内接圆半径为外接圆一半,外接圆半径变大,角数变多,颜色变红
好了,预热完了,下面进入正题
四、粒子运动
无论什么语言只有能模拟
时间流
就可以有粒子动画
粒子动画的基础在Android原生绘图之让你了解View的运动里讲的很详细
思想对于所有语言都是通用的,不仅限于java,有兴趣的可以详细了解下
1.废话不多说,来个运动盒
再说
运动盒
就是小球在盒子里不断弹跳的动画,就像这样
1.1:新建文件:run_ball_view.dart----->RunBallView + Ball
先把小球的实体类写一下
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, this.y, this.color, this.r, this.aX, this.aY, this.vX, this.vY});
}
1.2:画板准备好
class RunBallView extends CustomPainter {
Paint mPaint;
BuildContext context;
Ball _ball;
Rect _limit;
RunBallView(this.context, Ball ball, Rect limit) {
mPaint = new Paint();
_ball = ball;
_limit = limit;
}
@override
void paint(Canvas canvas, Size size) {
var winSize = MediaQuery.of(context).size;
canvas.translate(160, 320);
mPaint.color = Color.fromARGB(148, 198, 246, 248);
canvas.drawRect(_limit, mPaint);
canvas.save();
drawBall(canvas, _ball);
canvas.restore();
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
//绘制小球
void drawBall(Canvas canvas, Ball ball) {
mPaint.color = ball.color;
canvas.drawCircle(Offset(ball.x, ball.y), ball.r, mPaint);
}
}
1.3:认识一下矩形对象Rect
这是我们需要的字段
var limit = Rect.fromLTRB(-140, -100, 140, 100);
print("width:${limit.width}");
print("height:${limit.height}");
print("left:${limit.left}");
print("top:${limit.top}");
print("right:${limit.right}");
print("bottom:${limit.bottom}");
I/flutter (28755): width:280.0
I/flutter (28755): height:200.0
I/flutter (28755): left:-140.0
I/flutter (28755): top:-100.0
I/flutter (28755): right:140.0
I/flutter (28755): bottom:100.0
1.4:新建文件:run_ball_pager.dart---->RunBallPage
这里只需要一个时间流,用
AnimationController
一个人就够了
class RunBallPage extends StatefulWidget {
@override
_RunBallPageState createState() => _RunBallPageState();
}
class _RunBallPageState extends State<RunBallPage>
with SingleTickerProviderStateMixin {
AnimationController controller;
Ball _ball;
var _limit = Rect.fromLTRB(-140, -100, 140, 100);//矩形边界
@override
void initState() {
super.initState();
//初始化小球
_ball = Ball(x: 0, y: 0, color: Colors.blue, r: 10, aX: 0, aY: 0, vX: 0, vY: 2);
// 创建 AnimationController 对象
//|----vsync时会防止屏幕外动画(动画的UI不在当前屏幕时)消耗不必要的资源
controller = AnimationController(
duration: const Duration(milliseconds: 200000), vsync: this);
controller.addListener(() {
updateBall();//更新小球
setState(() {});
});
controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.reverse();
} else if (status == AnimationStatus.dismissed) {
controller.forward();
}
});
}
@override
void dispose() {
super.dispose();
controller.dispose(); // 资源释放
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("张风捷特烈"),
),
body: CustomPaint(
painter: RunBallView(context, _ball,_limit),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
controller.forward(); //执行动画
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
//更新小球
void updateBall() {}
}
小球.png
这样静态小球就完成了
1.5:更新新小球位置,渲染视图:RunBallPage#updateBall
上下弹动.gif
//更新小球位置
void updateBall() {
//运动学公式
_ball.x += _ball.vX;
_ball.y += _ball.vY;
_ball.vX += _ball.aX;
_ball.vY += _ball.aY;
//限定下边界
if (_ball.y > _limit.bottom - _ball.r) {
_ball.y = _limit.bottom - _ball.r;
_ball.vY = -_ball.vY;
_ball.color=randomRGB();//碰撞后随机色
}
//限定上边界
if (_ball.y < _limit.top + _ball.r) {
_ball.y = _limit.top + _ball.r;
_ball.vY = -_ball.vY;
_ball.color=randomRGB();//碰撞后随机色
}
}
1.6:运动盒的实现
运动盒.gif//初始时附加信息
_ball = Ball(x: 0, y: 0, color: Colors.blue, r: 10, aX: 0, aY: 0.1, vX: 2, vY: -2);
//更新小球位置
void updateBall() {
//运动学公式
_ball.x += _ball.vX;
_ball.y += _ball.vY;
_ball.vX += _ball.aX;
_ball.vY += _ball.aY;
//限定下边界
if (_ball.y > _limit.bottom - _ball.r) {
_ball.y = _limit.bottom - _ball.r;
_ball.vY = -_ball.vY;
_ball.color=randomRGB();//碰撞后随机色
}
//限定上边界
if (_ball.y < _limit.top + _ball.r) {
_ball.y = _limit.top + _ball.r;
_ball.vY = -_ball.vY;
_ball.color=randomRGB();//碰撞后随机色
}
//限定左边界
if (_ball.x < _limit.left + _ball.r) {
_ball.x = _limit.left + _ball.r;
_ball.vX = -_ball.vX;
_ball.color=randomRGB();//碰撞后随机色
}
//限定右边界
if (_ball.x > _limit.right - _ball.r) {
_ball.x = _limit.right - _ball.r;
_ball.vX= -_ball.vX;
_ball.color=randomRGB();//碰撞后随机色
}
}
2.粒子的运动
思路就是:用List把球装一下,碰撞的时候,创建一个方向相反,半径减半的球,加入集合
并将当前的球半径减半,效果挺不错的,实现起来也不麻烦。Android-java版可见
2.1:改动:RunBallPage
半径小于0.3就移除,为了不让小球无限增加,小于0.3基本上也就卡不见了
当然你也可以自定义移除的时机
var _balls = List<Ball>();//将_ball换成集合
var ball = Ball(x: 0, y: 0, color: Colors.blue, r: 40, aX: 0.05, aY: 0.1, vX: 3, vY: -3);
_balls.add(ball);//添加一个
//更新方法
for (int i = 0; i < _balls.length; i++) {
var ball = _balls[i];
if (ball.r < 0.3) {
//半径小于0.3就移除
_balls.removeAt(i);
}
//运动学公式
ball.x += ball.vX;
ball.y += ball.vY;
ball.vX += ball.aX;
ball.vY += ball.aY;
//限定下边界
if (ball.y > _limit.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 = _limit.bottom;
ball.vY = -ball.vY;
ball.color = randomRGB(); //碰撞后随机色
}
//限定上边界
if (ball.y < _limit.top) {
ball.y = _limit.top;
ball.vY = -ball.vY;
ball.color = randomRGB(); //碰撞后随机色
}
//限定左边界
if (ball.x < _limit.left) {
ball.x = _limit.left;
ball.vX = -ball.vX;
ball.color = randomRGB(); //碰撞后随机色
}
//限定右边界
if (ball.x > _limit.right) {
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.x = _limit.right;
ball.vX = -ball.vX;
ball.color = randomRGB(); //碰撞后随机色
}
}
}
//传入画布也变成小球集合
body: CustomPaint(
painter: RunBallView(context, _balls, _limit),
),
2.2:绘制小球时:RunBallView
:
把小球都绘制出来就行了
//复制一个小球
Ball.fromBall(Ball ball) {
this.x = ball.x;
this.y = ball.y;
this.color = ball.color;
this.r = ball.r;
this.aX = ball.aX;
this.aY = ball.aY;
this.vX = ball.vX;
this.vY = ball.vY;
}
//------paint方法中-----------
_balls.forEach((ball) {
drawBall(canvas, ball);
});
这样就完成了,是不是没有想象中的那么复杂
五、粒子时钟
这里就不详细分析,这里的Java版已经分析的很细致了,直接上代码(基本上是Java的翻译版)
这个效果新建了一个页面来做,digit三维数组Dart版附在文尾
1.渲染数字
绘制1994.png/**
* 渲染数字
* @param num 要显示的数字
* @param canvas 画布
*/
void renderDigit(int num, Canvas canvas) {
if (num > 10) {
return;
}
for (int i = 0; i < digit[num].length; i++) {
for (int j = 0; j < digit[num][j].length; j++) {
if (digit[num][i][j] == 1) {
canvas.save();
double rX = j * 2 * (_radius + 1) + (_radius + 1); //第(i,j)个点圆心横坐标
double rY = i * 2 * (_radius + 1) + (_radius + 1); //第(i,j)个点圆心纵坐标
canvas.translate(rX, rY);
mPaint.color = Colors.blue;
canvas.drawPath(mStarPath, mPaint);
canvas.restore();
}
}
}
}
2.画布中绘制1994
canvas.save();
renderDigit(1, canvas);
canvas.translate(80, 0);
renderDigit(9, canvas);
canvas.translate(80, 0);
renderDigit(9, canvas);
canvas.translate(80, 0);
renderDigit(4, canvas);
canvas.restore();
3.绘制时间
绘制时间.png3.1:时间的简单获取
DateTime now = new DateTime.now();
var hour = now.hour;
var second = now.second;
var minute = now.minute;
print("hour$hour");//15
print("second$second");//57
print("minute$minute");//27
3.2:绘制时间
//时
renderDigit(_now.hour ~/ 10, canvas);
canvas.translate(19 * _radius, 0);
renderDigit(_now.hour % 10, canvas);
//:
canvas.translate(19 * _radius, 0);
renderDigit(10, canvas);
//分
canvas.translate(11 * _radius, 0);
renderDigit(_now.minute ~/ 10, canvas);
canvas.translate(19 * _radius, 0);
renderDigit(_now.minute % 10, canvas);
//:
canvas.translate(18 * _radius, 0);
renderDigit(10, canvas);
//秒
canvas.translate(11 * _radius, 0);
renderDigit(_now.second ~/ 10, canvas);
canvas.translate(19 * _radius, 0);
renderDigit(_now.second % 10, canvas);
3.3:用Animation动起来
运动时钟.gif4.加小球
时钟方法基本上是Java版改些的,这里不分析了,可以看Java版的分析,基本上一致
//在类的外部定义全局的变量
var currTime = new DateTime.now();
var tagOfBall = new DateTime.now().millisecondsSinceEpoch;
var _balls = new List<Ball>();
//构造方法中刷新小球
ClockView(this.context) {
mPaint = new Paint();
mStarPath = nStarPath(5, _radius, _radius / 2);
_now = new DateTime.now();
addBallsChanged();
updateBalls();
}
/**
* 添加倒计时中改动的点---变动加球
*/
void addBallsChanged() {
var now = new DateTime.now();
if (currTime.second != now.second) {
//判断当前时间是否改变,再将点位放到集合中
if ((currTime.hour ~/ 10) != (now.hour ~/ 10)) {
addBalls((-17 * 5 - 11 * 2) * _radius.toInt(), currTime.hour ~/ 10);
}
if ((currTime.hour % 10) != (now.hour % 10)) {
addBalls((-17 * 4 - 11 * 2) * _radius.toInt(), currTime.hour % 10);
}
if ((currTime.minute ~/ 10) != (now.minute ~/ 10)) {
addBalls((-18 * 3 - 11) * _radius.toInt(), currTime.minute ~/ 10);
}
if ((currTime.minute % 10) != (now.minute % 10)) {
addBalls((-18 * 2 - 11) * _radius.toInt(), currTime.minute % 10);
}
if ((currTime.second ~/ 10) != (now.second ~/ 10)) {
addBalls(-18 * _radius.toInt(), currTime.second ~/ 10);
}
if ((currTime.second % 10) != (now.second % 10)) {
addBalls(0, currTime.second % 10);
currTime = now;
}
}
}
//添加小球
addBalls(int offsetX, int num) {
Random random = new Random();
for (int i = 0; i < digit[num].length; i++) {
for (int j = 0; j < digit[num][i].length; j++) {
if (digit[num][i][j] == 1) {
Ball ball = new Ball();
ball.aY = 0.1;
ball.vX = pow(-1, random.nextInt(1000)) * 6 * random.nextDouble();
ball.vY = 4 * random.nextDouble();
ball.x =
offsetX + j * 2 * (_radius + 1) + (_radius + 1); //第(i,j)个点圆心横坐标
ball.y = i * 2 * (_radius + 1) + (_radius + 1); //第(i,j)个点圆心纵坐标
ball.color = randomRGB();
ball.r = _radius;
_balls.add(ball);
}
}
}
}
/**
* 更新所有球的位置---让球运动
* 并且越界移除
*/
void updateBalls() {
double maxX = 400; //限定x范围大值
for (Ball ball in _balls) {
ball.x += ball.vX; //x=xo+v*t-----t=1
ball.y += ball.vY;
ball.y += ball.aY; //v=vo+a*t-----t=1
if (ball.y >= 160) {
//超过Y底线,反弹
ball.y = 160;
ball.vY = -ball.vY * 0.99;
}
if (ball.x > maxX) {
//超过X最大值,反弹
ball.x = maxX;
ball.vX = -ball.vX * 0.99;
}
}
//5秒清一次屏
if (new DateTime.now().millisecondsSinceEpoch - tagOfBall > 5000) {
_balls.clear();
tagOfBall = new DateTime.now().millisecondsSinceEpoch;
}
}
好了,今天就这样,内容有点多,现在Dart的语法越用越熟练了
后记:捷文规范
1.本文成长记录及勘误表
项目源码 | 日期 | 备注 |
---|---|---|
V0.1-github | 2018-12-17 | Flutter第2天--Animation动画+粒子运动 |
2.更多关于我
笔名 | 微信 | 爱好 | |
---|---|---|---|
张风捷特烈 | 1981462002 | zdl1994328 | 语言 |
我的github | 我的简书 | 我的掘金 | 个人网站 |
3.声明
1----本文由张风捷特烈原创,转载请注明
2----欢迎广大编程爱好者共同交流
3----个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正
4----看到这里,我在此感谢你的喜欢与支持
icon_wx_200.png
网友评论