本文所有原始碼見
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...
結語
本文到此接近尾聲了,如果想快速嚐鮮Flutter,《Flutter七日》會是你的必備佳品;如果想細細探究它,那就跟隨我的腳步,完成一次Flutter之旅。
另外本人有一個Flutter微信交流群,歡迎小夥伴加入,共同探討Flutter的問題,本人微訊號:zdl1994328
,期待與你的交流與切磋。
本文所有原始碼見
github/flutter_journey