什么是动画
动画(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'),
网友评论