本文全部源碼見
github/flutter_journeygit
見字如面,會動的畫面。畫面連續渲染,當速度快到必定程度,大腦就會呈現動感github
1).何爲運動:視覺上看是一個物體在不一樣的時間軸上表現出不一樣的物理位置
2).位移 = 初位移 + 速度 * 時間 小學生的知識很少說
3).速度 = 初速度 + 加速度 * 時間 初中生的知識很少說
4).時間、位移、速度、加速度構成了現代科學的運動體系
複製代碼
那刷新要有多快呢?不知你是否聽過FPS,對就是那個遊戲裏很重要的FPScanvas
FPS : Frames Per Second 畫面每秒傳輸幀數(新率) 單位赫茲(Hz)
60Hz的刷新率刷也就是指屏幕一秒內刷新60次,即60幀/秒
其中常見的電影24fps,也就是一秒鐘刷新24次。
要達到流暢,須要60fps,這也是遊戲中的一個指標,不然就會感受不流暢
一秒鐘刷新60次,即16.66667ms刷新一次,這也是一個常見的值
複製代碼
能夠用代碼模擬運動,不斷刷新的同時改變運動物體的屬性從而造成動畫
在Android中有ValueAnimator
,JavaScript(瀏覽器)中有``.數組
1.時間:無限執行----模擬時間流,每次刷新時間間隔,記爲:1T
2.位移:物體在屏幕像素位置----模擬世界,每一個像素距離記爲:1px
3.速度(單位px/T)、加速度(px/T^2)
注意:不管什麼語言,只要可以模擬時間與位移,本篇的思想均可以適用,只是語法不一樣罷了
複製代碼
經過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;//從新賦值
});
}
}
複製代碼
又到了咱們的Canvas了bash
///小球信息描述類
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),
),
);
複製代碼
也就是控制小球在每次刷新時改變其屬性,這樣視覺上就是運動狀態
在邊界碰撞後,改變方向便可,經過下面三步,一個運動盒就完成了微信
//[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();//碰撞後隨機色
}
}
}
複製代碼
給定一個較小的dx,隨着dx增長,根據函數求出dy,而後更新小球信息
以下面的sin圖像,隨着每次更新,根據函數關係約束小球座標值dom
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;
}
複製代碼
或者讓小球按圓形軌跡運動,下面是經過參數方程讓呈圓形軌跡
也就是數學學得好,想怎麼跑怎麼跑。ide
double dx=0.0;
void updateBall(){
dx+=pi/180;//每次dx增長pi/180
_ball.x+=cos(dx);
_ball.y+=sin(dx);
}
複製代碼
一個粒子運動已經夠好玩的,那麼許多粒子會怎麼樣?
須要改變的是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));
}
複製代碼
也許你以爲畫小球沒什麼,但要知道,小球只是單體,
你能夠換成任意你能繪製的東西,甚至是圖片或組件
也就是在恰當的時機能夠添加粒子而達到必定的視覺效果
核心是當到達邊界後進行處理,將原來的粒子半徑減半,再添加一個等大反向的粒子
//限定下邊界
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);
}
//略...
}
複製代碼
如今能夠感覺到,動畫就是元素的信息在不斷變化,給人產生的感受
只要將信息描述好,那麼你能夠完成任何動畫,你就是創造者與主宰者
/**
* 渲染數字
* @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...
本文到此接近尾聲了,若是想快速嚐鮮Flutter,《Flutter七日》會是你的必備佳品;若是想細細探究它,那就跟隨個人腳步,完成一次Flutter之旅。
另外本人有一個Flutter微信交流羣,歡迎小夥伴加入,共同探討Flutter的問題,本人微信號:zdl1994328
,期待與你的交流與切磋。
本文全部源碼見
github/flutter_journey