什麼是動畫
動畫(Animation)是一系列靜止的影像以一定頻率連續變化而導致肉眼的視覺暫留。
為什麼需要動畫
無論是移動端還是Web端開發,當有和使用者互動的場景時,引入動畫,可以使應用體驗更流暢、使用者體驗更好,增強產品的可用性與趣味性。在 Flutter 中建立動畫可以有多種不同實現方式,可以輕鬆實現各種動畫型別。在 Flutter 中動畫可以理解為,Widget 在某一段時間內,從一個狀態(大小、位置、顏色、透明度、字型等等)變化到另一個狀態的過程。
Flutter開發中如何選擇動畫
上圖從 I want a Flutter animation! 出發,最終有四個終點,可以指導我們選擇動畫的實現方式,從上到下複雜程度遞增。
- Implicit Animations
- Explicit Animations
- Low-Level Animations
- Third-Party Animation Framework
基礎概念
Animation
儲存動畫目前的狀態(例如,是否開始,暫停,前進或倒退)。
abstract class Animation<T> extends Listenable implements ValueListenable<T> {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const Animation();
///...
/// The current status of this animation.
AnimationStatus get status;
/// The current value of the animation.
@override
T get value;
複製程式碼
AnimationController
管理 Animation,控制動畫開始、結束等
class AnimationController extends Animation<double>
with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {
///...
}
複製程式碼
Curve
字面意思為曲線,可以理解為兩個值的變化曲線,比如勻速、先加速後減速、減速等等
abstract class Curve extends ParametricCurve<double> {
/// Abstract const constructor to enable subclasses to provide
/// const constructors so that they can be used in const expressions.
const Curve();
///...
}
複製程式碼
Flutter 內建了多種型別的 Curve。
Tween
為動畫物件設定變化範圍值。
class Tween<T extends dynamic> extends Animatable<T> {
/// Creates a tween.
///
/// The [begin] and [end] properties must be non-null before the tween is
/// first used, but the arguments can be null if the values are going to be
/// filled in later.
Tween({
this.begin,
this.end,
});
}
複製程式碼
Ticker
用來註冊螢幕重新整理的回撥來驅動動畫的執行。
隱式動畫
最簡單的動畫,隱式動畫一般繼承自 ImplicitlyAnimatedWidget
,之所以叫隱式動畫,在使用的時候 Flutter 幫我們隱藏了一些細節,我們無需關注 Animation 和 AnimationController 的建立,只需要關注 Widget 從什麼狀態變成了什麼狀態即可。當然隱式動畫並非沒有 Animation 和 AnimationController 只不過在父類 ImplicitlyAnimatedWidget
Flutter 幫我們建立好了,ImplicitlyAnimatedWidget
主要程式碼如下:
abstract class ImplicitlyAnimatedWidget extends StatefulWidget {
const ImplicitlyAnimatedWidget({
Key key,
this.curve = Curves.linear,
@required this.duration,
this.onEnd,
}) : assert(curve != null),
assert(duration != null),
super(key: key);
final Curve curve;
final Duration duration;
final VoidCallback onEnd;
@override
ImplicitlyAnimatedWidgetState<ImplicitlyAnimatedWidget> createState();
}
abstract class ImplicitlyAnimatedWidgetState<T extends ImplicitlyAnimatedWidget> extends State<T> with SingleTickerProviderStateMixin<T> {
@protected
AnimationController get controller => _controller;
AnimationController _controller;
/// The animation driving this widget's implicit animations.
Animation<double> get animation => _animation;
Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: widget.duration,
debugLabel: kDebugMode ? widget.toStringShort() : null,
vsync: this,
);
_controller.addStatusListener((AnimationStatus status) {
switch (status) {
case AnimationStatus.completed:
if (widget.onEnd != null)
widget.onEnd();
break;
case AnimationStatus.dismissed:
case AnimationStatus.forward:
case AnimationStatus.reverse:
}
});
_updateCurve();
_constructTweens();
didUpdateTweens();
}
///...
}
複製程式碼
常用的隱式動畫有
- AnimatedContainer
- AnimatedAlign
- AnimatedCrossFade
- AnimatedDefaultTextStyle
- AnimatedOpacity
- AnimatedPadding
- AnimatedPhysicalModel
- AnimatedPositioned
- AnimatedSize
- AnimatedSwitcher
- AnimatedTheme
AnimatedContainer
為 Container 新增動畫為例:
InkWell(
onTap: () {
setState(() {
selected = !selected;
});
},
child: AnimatedContainer(
color: Colors.blue,
curve: _curve,
margin: const EdgeInsets.all(20),
duration: Duration(seconds: 1),
width: selected ? 200.0 : 100.0,
height: selected ? 100.0 : 200.0,
),
),
複製程式碼
顯式動畫
顯式動畫比隱式動畫稍複雜一點,需要我們指定 Animation 和 AnimationController。顯式動畫一般繼承自 AnimatedWidget
。
常用的顯式動畫有
- AlignTransition
- ScaleTransition
- SizeTransition
- SlideTransition
- PositionedTransition
- RelativePositionedTransition
class _SizeTransitionDemoState extends State<SizeTransitionDemo>
with TickerProviderStateMixin {
AnimationController _controller;
Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 3),
vsync: this,
);
// ..repeat();
_animation = CurvedAnimation(
parent: _controller,
curve: Curves.fastOutSlowIn,
);
_controller.repeat();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("SizeTransition"),
actions: [
IconButton(icon: Icon(Icons.settings), onPressed: () {}),
],
),
body: SingleChildScrollView(
child: Column(
children: [
SizeTransition(
sizeFactor: _animation,
axis: Axis.horizontal,
// axisAlignment: -1,
child: Center(
child: FlutterLogo(size: 200.0),
),
),
],
),
),
);
}
}
複製程式碼
自定義CustomPainter
自定義CustomPainter可以理解為顯示動畫的高階用法,通過自定義畫筆來實現對動畫的更精細控制。
class LinePainter extends CustomPainter {
final double progress;
LinePainter(this.progress);
@override
void paint(Canvas canvas, Size size) {
var paint = Paint()
..color = Colors.yellowAccent
..strokeWidth = 5
..strokeCap = StrokeCap.round;
Offset startingPoint = Offset(0, size.height / 2);
Offset endingPoint = Offset(progress, size.height / 2);
canvas.drawLine(startingPoint, endingPoint, paint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
複製程式碼
Lottie
用 Lottie 執行設計師設計的複雜動畫,一般為 json 格式。
Lottie.network("https://assets7.lottiefiles.com/packages/lf20_wXxy5o.json", width: 300, height: 300),
Lottie.network("https://assets3.lottiefiles.com/packages/lf20_r9lh4ebq.json", width: 300, height: 300),
Lottie.asset('assets/64586-like-and-dislike-button.json'),
Lottie.asset('assets/65014-dog-walking.json'),
Lottie.asset('assets/65077-breathing-lotus.json'),
複製程式碼