到目前為止建立的動畫,都是單一效果,有沒有辦法一個動畫中含有多個效果呢?例如〈繪圖與動畫〉中,圓畫一半後略為放大再畫剩下的一半?
在〈自訂緩動曲線〉中談到,緩動曲線的時間標準化為 0.0 到 1.0,Flutter 提供了 CurvedAnimation
,可以讓你決定在緩動曲線的哪一個時間區隔,套用指定的緩動曲線,例如:
...
class _AnimatedPie extends State<AnimatedPie> with SingleTickerProviderStateMixin {
AnimationController _animation;
Animation<double> radian1;
Animation<double> scale;
Animation<double> radian2;
@override
void initState() {
super.initState();
_animation = AnimationController(
duration: widget.duration,
vsync: this,
)..repeat(); // 不斷重複
radian1 = Tween<double>(
begin: 0,
end: pi
).animate(CurvedAnimation( // 使用 CurvedAnimation
parent: _animation,
curve: Interval(0, 0.33), // 在時間軸的 0 ~ 0.33 部份套用
));
scale = Tween<double>(
begin: 1,
end: 1.1
).animate(CurvedAnimation(
parent: _animation,
curve: Interval(0.34, 0.66), // 在時間軸的 0.34 ~ 0.66 部份套用
));
radian2 = Tween<double>(
begin: 0,
end: pi
).animate(CurvedAnimation(
parent: _animation,
// 在時間軸的 0.66 ~ 1.0 部份套用
curve: Interval(0.67, 1.0,
curve: Curves.ease // 可設定 curve
),
));
}
...
Interval
的 curve
還可以指定緩動曲線種類,預設是 Curves.linear
,在設計 radian1
、scale
、radian2
這些 Animation<double>
時,除了時間區設定之外,另外還要思考的是,這些 Animation<double>
的 value
要做哪種用途,或者是如何結合計算來完成想要的動畫,例如,scale
的 value
可用來乘上 widget.radius
,而 radian1.value + radian2.value
的結果,可以用來設定給 sweepAngle
:
...
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (_, __) {
return Pie(
radius: widget.radius * scale.value,
sweepAngle: radian1.value + radian2.value
);
}
);
}
...
有點像是在編輯時間軸的概念,只是要多點計算,來個完整的程式示範:
import 'package:flutter/material.dart';
import 'dart:math';
void main() => runApp(
MaterialApp(
home: Center(
child: AnimatedPie(radius: 50, duration: Duration(seconds: 2)),
),
)
);
class AnimatedPie extends StatefulWidget {
final double radius;
final Duration duration;
AnimatedPie({this.radius, this.duration});
@override
State<StatefulWidget> createState() => _AnimatedPie();
}
class _AnimatedPie extends State<AnimatedPie> with SingleTickerProviderStateMixin {
AnimationController _animation;
Animation<double> radian1;
Animation<double> scale;
Animation<double> radian2;
@override
void initState() {
super.initState();
_animation = AnimationController(
duration: widget.duration,
vsync: this,
)..repeat(); // 不斷重複
radian1 = Tween<double>(
begin: 0,
end: pi
).animate(CurvedAnimation(
parent: _animation,
curve: Interval(0, 0.33),
));
scale = Tween<double>(
begin: 1,
end: 1.1
).animate(CurvedAnimation(
parent: _animation,
curve: Interval(0.34, 0.66),
));
radian2 = Tween<double>(
begin: 0,
end: pi
).animate(CurvedAnimation(
parent: _animation,
curve: Interval(0.67, 1.0, curve: Curves.ease),
));
}
@override
void dispose() {
_animation.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (_, __) {
return Pie(
radius: widget.radius * scale.value,
sweepAngle: radian1.value + radian2.value
);
}
);
}
}
class Pie extends StatelessWidget {
final startAngle = pi * 1.5;
final double radius;
final double sweepAngle;
Pie({this.radius, this.sweepAngle});
@override
Widget build(BuildContext context) {
return CustomPaint(
size: Size.infinite,
painter: PiePainter(radius: radius, sweepAngle: sweepAngle),
);
}
}
class PiePainter extends CustomPainter {
final startAngle = pi * 1.5;
final double radius;
final double sweepAngle;
PiePainter({this.radius, this.sweepAngle});
@override
void paint(Canvas canvas, Size size) {
Paint paint = Paint()
..color = Colors.blue
..strokeWidth = radius / 2
..style = PaintingStyle.stroke;
canvas.drawArc(
Rect.fromCircle(center: Offset(size.width / 2, size.height / 2), radius: radius),
startAngle, sweepAngle,
false,
paint
);
}
@override
bool shouldRepaint(PiePainter other) => other.radius != radius || other.sweepAngle != sweepAngle;
}
完成的效果如下: