1.前言
1.1:Flutter動畫中:
首先要看的是Flutter中動畫的幾個類之間的關係:
主角當然是我們的Animation類了,它可以藉助Animatable進行強化
Animatable通過animate函式接收一個Animation物件,再返回Animation物件,這不就是包裝嗎?
通過Animation物件回撥即可獲取規律變畫的值,進行渲染。這是動畫的基本。
1.2:Animation和Animation體系一覽
整個Flutter的Animation相比Android還是比較簡單的
1.3:介紹今天的主角nStarPath
我們通過變動這個函式中的引數讓路徑動態變化實現動畫
/// 可以建立一個外接圓半徑為[R],內接圓半徑半徑為[r]的[num]角星路徑
Path nStarPath(int num, double R, double r) {
Path path = new Path();
double perDeg = 360 / num;
double degA = perDeg/2/2;
double degB = (360 / (num - 1) - degA) / 2 + degA;
path.moveTo(cos(_rad(degA)) * R, (-sin(_rad(degA)) * R));
for (int i = 0; i < num; i++) {
path.lineTo(
cos(_rad(degA + perDeg * i)) * R, -sin(_rad(degA + perDeg * i)) * R);
path.lineTo(
cos(_rad(degB + perDeg * i)) * r, -sin(_rad(degB + perDeg * i)) * r);
}
path.close();
return path;
}
double _rad(double deg) {
return deg * pi / 180;
}
複製程式碼
1.4:動畫舞臺的搭建
對於動畫的演示,最好的當然是繪製了,繪製中最好的當然是我的五角星了
感覺建立StatefulWidget的程式碼開始時基本一致,寫了一篇模板解析器
玩轉字串篇--Gradle+程式碼生成器=懶人必備
import 'package:flutter/material.dart';
class AnimPage extends StatefulWidget {
@override
_AnimPageState createState() => _AnimPageState();
}
class _AnimPageState extends State<AnimPage>{
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Flutter之旅"),
),
body: CustomPaint(
painter: AnimView(),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
//TODO 執行動畫
},
child: Icon(Icons.add),
),
);
}
}
class AnimView extends CustomPainter {
Paint mPaint;
Paint gridPaint;
AnimView() {
mPaint = new Paint();
gridPaint = Paint()
..style = PaintingStyle.stroke
..color = Colors.cyanAccent;
mPaint.color = Colors.deepOrange;
}
@override
void paint(Canvas canvas, Size size) {
canvas.drawPath(gridPath(area: Size(500, 1000)), gridPaint);//繪製網格
canvas.translate(200, 200);
canvas.drawPath(nStarPath(5, 100, 50), mPaint);//繪製五角星
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
/// 可以建立一個外接圓半徑為[R],內接圓半徑半徑為[r]的[num]角星,
Path nStarPath(int num, double R, double r) {
Path path = new Path();
double perDeg = 360 / num;
double degA = perDeg/2/2;
double degB = (360 / (num - 1) - degA) / 2 + degA;
path.moveTo(cos(_rad(degA)) * R, (-sin(_rad(degA)) * R));
for (int i = 0; i < num; i++) {
path.lineTo(
cos(_rad(degA + perDeg * i)) * R, -sin(_rad(degA + perDeg * i)) * R);
path.lineTo(
cos(_rad(degB + perDeg * i)) * r, -sin(_rad(degB + perDeg * i)) * r);
}
path.close();
return path;
}
double _rad(double deg) {
return deg * pi / 180;
}
///建立一個區域是[area],小格邊長為[step]的網格的路徑
Path gridPath({double step = 20, Size area}) {
Path path = Path();
for (int i = 0; i < area.height / step + 1; i++) {
//畫橫線
path.moveTo(0, step * i); //移動畫筆
path.lineTo(area.width, step * i); //畫直線
}
for (int i = 0; i < area.width / step + 1; i++) {
//畫縱線
path.moveTo(step * i, 0);
path.lineTo(step * i, area.height);
}
return path;
}
複製程式碼
好了,現在開始Flutter的動畫之旅
2.Flutter動畫基本使用
這裡再貼一下這張Animation使用圖:
2.1:動畫的基本使用:Tween+AnimationController
1.讓_AnimPageState類with一下SingleTickerProviderStateMixin
2.使用建立一個AnimationController物件(Animation族)
3.複寫SingleTickerProviderStateMixin的dispose方法釋放AnimationController物件
4.建立Tween物件(Animatable族)並呼叫animate方法,生成新的Animation物件
5.監聽Animation的變化,獲取每次重新整理時的值。
class _AnimPageState extends State<AnimPage> with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<double> animation;
@override
void initState() {
super.initState();
controller = AnimationController(////建立 Animation物件
duration: const Duration(milliseconds: 2000), //時長
vsync: this);
var tween = Tween(begin: 25.0, end: 150.0); //建立從25到150變化的Animatable物件
animation = tween.animate(controller); //執行animate方法,生成
animation.addListener(() {
print(animation.value);
});
}
@override
void dispose() {
super.dispose();
controller.dispose(); // 資源釋放
}
@override
Widget build(BuildContext context) {
return Scaffold(
//略同...
floatingActionButton: FloatingActionButton(
onPressed: () {
controller.forward(); //執行動畫
},
//略同...
);
}
}
複製程式碼
注:有時候為了方便可以連寫,關於
SingleTickerProviderStateMixin
這裡不做深究,
但要知道,既然是mixin就是給類附加能力的,其中之一便是dispose()
方法
animation = Tween(begin: 25.0, end: 150.0).animate(controller)
..addListener(() {
print(animation.value);
});
複製程式碼
看一下控制檯列印結果:從25~150變化的一群數字
---->[控制檯列印]----
I/flutter ( 9073): 25.0
I/flutter ( 9073): 26.1205625
I/flutter ( 9073): 27.2418125
I/flutter ( 9073): 28.363125
出處略去n行....
I/flutter ( 9073): 147.20725
I/flutter ( 9073): 148.3288125
I/flutter ( 9073): 149.4503125
I/flutter ( 9073): 150.0
複製程式碼
2.2:熱身運動,看一下Tween下點的軌跡
也是突發奇想,數字在不斷變化,這可都是白花花的資源啊,要不秀一個
這個小例子完美的闡述了Tween補間的動畫是勻速的
class AnimPage extends StatefulWidget {
@override
_AnimPageState createState() => _AnimPageState();
}
class _AnimPageState extends State<AnimPage>
with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<double> animation;
List<Offset> _points=[];//點集
@override
void initState() {
super.initState();
controller = AnimationController(//建立 Animation物件
duration: const Duration(milliseconds: 2000), //時長
vsync: this);
var tween = Tween(begin: 25.0, end: 150.0); //建立從25到150變化的Animatable物件
animation = tween.animate(controller); //執行animate方法,生成
animation.addListener(() {
render(_points,animation.value);
});
}
@override
void dispose() {
super.dispose();
controller.dispose(); // 資源釋放
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Flutter之旅"),
),
body: CustomPaint(
painter: AnimView(_points),//入參
),
floatingActionButton: FloatingActionButton(
onPressed: () {
controller.forward(); //執行動畫
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
double x=0;
//核心渲染方法,將值加入集合中並渲染
void render(List<Offset> _points, double value) {
_points.add(Offset(x, -value));
x++;
setState(() {//更新元件
});
}
}
class AnimView extends CustomPainter {
List<Offset> _points;
Paint mPaint;
Paint gridPaint;
AnimView(this._points) {
mPaint = new Paint();
gridPaint = Paint()
..style = PaintingStyle.stroke
..color = Colors.cyanAccent;
mPaint..color = Colors.deepOrange..strokeWidth=3;
}
@override
void paint(Canvas canvas, Size size) {
canvas.drawPath(gridPath(area: Size(500, 1000)), gridPaint);
canvas.translate(200,200);
canvas.drawCircle(Offset(0, 0), 2.5, gridPaint..color=Colors.black..style=PaintingStyle.fill);
_drawStar(canvas,_points);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
void _drawStar(Canvas canvas, List<Offset> pos) {
canvas.drawPoints(PointMode.lines, pos, mPaint);
}
}
///建立一個區域是[area],小格邊長為[step]的網格的路徑
Path gridPath({double step = 20, Size area}) {
Path path = Path();
for (int i = 0; i < area.height / step + 1; i++) {
//畫橫線
path.moveTo(0, step * i); //移動畫筆
path.lineTo(area.width, step * i); //畫直線
}
for (int i = 0; i < area.width / step + 1; i++) {
//畫縱線
path.moveTo(step * i, 0);
path.lineTo(step * i, area.height);
}
return path;
}
複製程式碼
2.3:建立星星的描述類和繪製
三個屬性,外接圓半徑,內接圓半徑和角數
class Star{
int num;
double R;
double r;
Star(this.num,this.R,this.r);
}
---->[AnimView類]----
class AnimView extends CustomPainter {
Star _star;
AnimView(this._star) {
//略同...
}
@override
void paint(Canvas canvas, Size size) {
//略同...
_drawStar(canvas,_star);
}
//繪製星星
void _drawStar(Canvas canvas, Star star) {
canvas.drawPath(nStarPath(star.num, star.R, star.r), mPaint);
}
}
---->[_AnimPageState類]----
class _AnimPageState extends State<AnimPage>
with SingleTickerProviderStateMixin {
Star _star;
@override
void initState() {
_star=Star(5, 100, 50);
//略同...
}
@override
Widget build(BuildContext context) {
//略同...
body: CustomPaint(
painter: AnimView(_star),
複製程式碼
2.3:動態更新
只需要在重新整理的時候更改五角星的屬性就行了,下面就是外接圓半徑25~150變化
animation.addListener(() {
render(_star,animation.value);
}
//核心渲染方法
void render(Star star, double value) {
star.R=value;
setState(() {//更新元件
});
}
複製程式碼
2.4:int資料的動畫:IntTween
Tween是兩個double型別的數字在一定的時間內的均勻變化
那int該腫麼辦?Tween之下有二十來個孩子用於不同的物件變化
其一便是IntTween
,這裡讓星星的角數從5~100不斷變化形成動畫
class _AnimPageState extends State<AnimPage>
with SingleTickerProviderStateMixin {
Animation<int> animation;//改成int泛型
//略同...
@override
void initState() {
//略同...
var intTween = IntTween(begin: 5, end: 100);
//略同...
}
//核心渲染方法
void render(Star star, int value) {
star.num=value;
setState(() {//更新元件
});
}
}
複製程式碼
實現起來還是比較簡單的
2.5:顏色變化: ColorTween
顧名思義,勻速改變顏色唄,思路是一致的,這裡先給Star描述類價格color欄位
在Canvas繪製時使用Satr的顏色,這樣在重新整理時就會呈現顏色漸變
class Star{
//略同...
Color color;
Star(this.num,this.R,this.r,this.color);
}
class _AnimPageState extends State<AnimPage>
with SingleTickerProviderStateMixin {
Animation<Color> animation;
//略同...
@override
void initState() {
_star=Star(5, 100, 60,Colors.red);
//略同...
var colorTween = ColorTween(begin: Colors.red, end: Colors.yellow);
//建立從紅到黃變化的Animatable物件
}
//核心渲染方法
void render(Star star, Color value) {
star.color=value;
setState(() {//更新元件
});
}
}
---->[AnimView:繪製時使用顏色]----
void _drawStar(Canvas canvas, Star star) {
canvas.drawPath(nStarPath(star.num, star.R, star.r), mPaint..color=star.color);
}
複製程式碼
3.讓動畫更有動感:CurveTween
看名字是曲線補間,也就是運動不再是勻速的,可以自己設計。
3.1:看一下CurveTween的原始碼
需要一個curve屬性,對應的是Curve物件。
Curve為抽象類,有一個四入參的子類Cubic,去吧,皮卡丘就決定是你了。
---->[CurveTween]----
class CurveTween extends Animatable<double> {
CurveTween({ @required this.curve })
: assert(curve != null);
Curve curve;
---->[Curve]----
@immutable
abstract class Curve {
---->[Curve]----
class Cubic extends Curve {
const Cubic(this.a, this.b, this.c, this.d)
複製程式碼
3.2:關於曲線引數的獲取
記得掘金的頭像可以轉,Chrome瀏覽器裡有個小功能,在除錯皮膚裡
看來一下有個lazy的樣式下的translation,點開可以除錯曲線,獲取四個值
用剛才的畫點方法看了一下資料的變動情況
3.3:程式碼操作
根據包裝設設計模式的思想,CurveTween可以強化Animation擁有從0~1的曲線,
然後再送到Tween中進行補間,讓其在兩個數的範圍內具有曲線補間能力
controller = AnimationController(//建立 Animation物件
duration: const Duration(milliseconds: 2000), //時長
vsync: this);
var curveTween = CurveTween(curve:Cubic(0.96, 0.13, 0.1, 1.2));//建立curveTween
var tween=Tween(begin: 50.0, end: 100.0);
animation = tween.animate(curveTween.animate(controller));
animation.addListener(() {
render(_star,animation.value);
});
複製程式碼
另外,Curves中也定義了41個常用的Curve,來方便使用,大家可以試試
4.動畫的監聽
4.1:運動狀態:AnimationStatus
相像一下,一個百米跑道標註著刻度,哨聲一響,你開始跑
enum AnimationStatus {
dismissed,//在正在開始時停止了?跌倒在起跑線上
forward,//運動中
reverse,//跑到終點,再跑回來的時候
completed,//跑到終點時
}
複製程式碼
4.2:為Animation新增監聽
通過
Animation#addStatusListener
可以回撥AnimationStatus
物件
animation.addStatusListener((status){
switch(status){
case AnimationStatus.completed:
controller.reverse();//反向
break;
case AnimationStatus.forward:
break;
case AnimationStatus.reverse:
_star.color=randomRGB();
break;
case AnimationStatus.dismissed:
controller.forward();
break;
}
});
複製程式碼
這樣,Animation基本用法就說完了,還有幾個類就不一一介紹了,基本使用都差不多
關於動畫效果,是一個永遠也無法滿足的深淵,它無法言盡。
一張經典的畫作重要的不是畫筆,而是握筆的人,你的動畫屬於你。
結語
本文到此接近尾聲了,如果想快速嚐鮮Flutter,《Flutter七日》會是你的必備佳品;如果想細細探究它,那就跟隨我的腳步,完成一次Flutter之旅。
另外本人有一個Flutter微信交流群,歡迎小夥伴加入,共同探討Flutter的問題,本人微訊號:zdl1994328
,期待與你的交流與切磋。
本文所有原始碼見
github/flutter_journey