前言
本文分享下自定義完成動畫的實現方法,最終的效果圖如下
首先是繪圖
在Flutter中繪圖非常簡單。關鍵詞CustomPainter
, CustomPaint
, Canvas
。
在iOS/Android中我們繼承UIView/View
重寫draw/onDraw
方法在裡面執行畫圖操作。
在Flutter中稍微有點不一樣,我們使用CustomPaint
(這是一個widget),它需要一個引數painter
,這個引數的型別是一個抽象類CustomPainter
。
我們需要實現這個類的兩個關鍵方法: paint
,shouldRepaint
。畫什麼就由paint
決定,而只有shouldRepaint
返回true的時候才會重繪。
實現void paint(Canvas canvas, Size size)
這個方法,在iOS中我們使用UIBezierPath
和Core Graphics
繪圖,在Flutter具體的繪製方法用這個canvas
, 具體的API可以檢視官方文件
下面這個例子就是畫一段圓弧
class DemoPainter extends CustomPainter {
final double _arcStart;
final double _arcSweep;
DemoPainter(this._arcStart, this._arcSweep);
@override
void paint(Canvas canvas, Size size) {
double side = math.min(size.width, size.height);
Paint paint = Paint()
..color = Colors.blue
..strokeCap = StrokeCap.round
..strokeWidth = 4.0
..style = PaintingStyle.stroke;
canvas.drawArc(
Offset.zero & Size(side, side), _arcStart, _arcSweep, false, paint);
}
@override
bool shouldRepaint(DemoPainter other) {
return _arcStart != other._arcStart || _arcSweep != other._arcSweep;
}
}
複製程式碼
使用的時候把DemoPainter
的例項當做引數傳給CustomPaint
就可以使用了,比如
Container(
child: CustomPaint(painter: DemoPainter(0.0, math.pi)),
height: 200.0,
width: 200.0,
color: Colors.deepOrange,
padding: EdgeInsets.all(30.0),
)
複製程式碼
顯示效果
然後加動畫
Flutter的動畫也不復雜,關鍵詞AnimationController
。
Flutter中的動畫是基於Animation
,這個物件本身是一個抽象類,在一段時間內依次產生一些值。我們使用封裝好的AnimationController
來做動畫,它在螢幕重新整理的每一幀,產生一個新的值,預設情況是在給定的時間段內線性的生成0.0到1.0的數字。
AnimationController
有個引數vsync
可以繫結到一個widget
(需要widget擴充套件SingleTickerProviderStateMixin
),當widget不顯示時,動畫定時器將會暫停,當widget再次顯示時,動畫定時器重新恢復執行。duration
屬性可以設定持續時間。還有一些方法可以控制動畫forward
啟動,reverse
反轉,repeat
重複。
AnimationController
有addListener
和addStatusListener
方法可以新增監聽,一個是值監聽一個是狀態監聽。值監聽常用在呼叫setState
來觸發UI重建來實現動畫,狀態監聽用在動畫狀態變化的時候執行一些方法,比如在動畫結束時反轉動畫。
至此我們已經可以繪製動畫了,程式碼如下
class DemoWidget extends StatefulWidget {
@override
_DemoWidgetState createState() => _DemoWidgetState();
}
class _DemoWidgetState extends State<DemoWidget>
with SingleTickerProviderStateMixin {
AnimationController _controller;
@override
void initState() {
_controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 1500))
..repeat()
..addListener(() {
setState(() {});
});
super.initState();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: DemoPainter(0.0, _controller.value * math.pi * 2),
);
}
}
複製程式碼
可以藉助AnimatedBuilder
改寫上文的initState
和build
方法,使檢視層級更加清楚,有助於封裝
@override
void initState() {
_controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 1500))
..repeat();
super.initState();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(animation: _controller, builder: (context, child) {
return CustomPaint(
painter: DemoPainter(0.0, _controller.value * math.pi * 2),
);
});
}
複製程式碼
Tween與Curve
Tween和Curve可以幫我們更好地控制Animation的值 一般的Animation會在給定的時間內線性的產生0.0到1.0的值
Tween可以把這些轉變成我們想要的型別或者是範圍
比如Tween(begin: math.pi * 1.5, end: math.pi * 1.5 + math.pi * 2).evaluate(_controller),
就可以把值的範圍轉成1.5pi到3.5pi。
Curve是一個抽象類表示生成值的曲線, Curves已經定義了許多常用的曲線。
這裡Tween
,Curve
可以使用chain
,evaluate
,transform
和Animation串起來使用
我們可以使用這些更改我們上文的例子,程式碼如下
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return CustomPaint(
painter: DemoPainter(
Tween(begin: math.pi * 1.5, end: math.pi * 3.5)
.chain(CurveTween(curve: Interval(0.5, 1.0)))
.evaluate(_controller),
math.sin(Tween(begin: 0.0, end: math.pi).evaluate(_controller)) *
math.pi,
),
);
});
}
複製程式碼
當然這裡第二個引數有更簡潔的寫法
math.sin(_controller.value*math.pi) *math.pi
複製程式碼
顯示效果
本文版權屬於再惠研發團隊,歡迎轉載,轉載請保留出處。@白爾摩斯