基本实现
Flutter
中有多种类型的动画,先从一个简单的例子开始。
使用一个 AnimatedContainer
控件,然后设置动画时长 duration
,最后调用 setState
方法改变需要变化的属性值,一个动画就创建了
import 'package:flutter/material.dart';
class AnimatedContainerPage extends StatefulWidget {
AnimatedContainerPage({Key key}) : super(key: key);
@override
_AnimatedContainerPageState createState() => _AnimatedContainerPageState();
}
class _AnimatedContainerPageState extends State<AnimatedContainerPage> {
// 初始的属性值
double size = 100;
double radius = 25;
Color color = Colors.yellow;
void _animate() {
// 改变属性值
setState(() {
this.size = this.size == 100 ? 200 : 100;
this.radius = this.radius == 25 ? 100 : 25;
this.color = this.color == Colors.yellow ? Colors.greenAccent : Colors.yellow;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Animated Container'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
AnimatedContainer(
width: this.size,
height: this.size,
curve: Curves.easeIn,
duration: Duration(seconds: 1),
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
color: this.color,
borderRadius: BorderRadius.circular(this.radius)
),
child: FlutterLogo(),
)
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: this._animate,
child: Icon(Icons.refresh),
),
);
}
}
基础概念
补间动画 - Tween Animation
- 根据初始状态,系统自动补充中间状态
基于物理的动画 - Physics-based animation
- 基于物理的动画是一种遵循物理学定律的动画形式
Animation
-
Flutter
中的动画系统基于Animation
对象 -
它是一个抽象类,保存了当前动画的值和状态(开始、暂停、前进、倒退),但不记录屏幕上显示的内容
-
UI
元素通过读取Animation
对象的值和监听状态变化运行build
函数,然后渲染到屏幕上形成动画效果 -
一个
Animation
对象在一段时间内会持续生成介于两个值之间的值,比较常见的类型是Animation<double>
,除double
类型之外还有Animation<Color>
或者Animation<Size>
等。 -
animation.dart
定义了动画的四种状态,以及核心的抽象类Animation
。
Animation
的四种状态:
dismissed
:动画的初始状态forward
:从头到尾播放动画reverse
:从尾到头播放动画completed
:动画完成的状态
Animation
类动画过程中值变化的回调
void addListener(VoidCallback listener);
void removeListener(VoidCallback listener);
Animation
类状态的回调
void addStatusListener(AnimationStatusListener listener);
void removeStatusListener(AnimationStatusListener listener);
AnimationController
-
AnimationController
派生自Animation
类。带有控制方法的Animation
对象,用来控制动画的启动,暂停,结束,设定动画运行时间等。 -
默认情况下,
AnimationController
是线性的产生0.0
到1.0
之间的值,每刷新一帧就产出一个数值。 -
AnimationController
在不使用的时候需要dispose
,否则会造成资源的泄漏
AnimationController
的功能有如下几种:
- 播放一个动画(
forward
或者reverse
),或者停止一个动画;- 设置动画的值;
- 设置动画的边界值;
- 创建基于物理的动画效果。
Tween
-
tween.dart
定义了一系列的估值器 -
Flutter
通过抽象类Animatable
来实现估值器 -
用来生成不同类型和范围的动画取值
-
最主要的子类是
Tween
,一个线性的估值器
以下是在
Tween
基础上实现的几种不同类型的估值器
ReverseTween
ColorTween
SizeTween
RectTween
IntTween
StepTween
ConstantTween
Curve
-
curve.dart
:插值器 -
使用
CurvedAnimation
可以将时间曲线定义为非线性曲线 -
Curve
也是一个抽象类,定义了时间与数值的一个接口
Flutter
定义了一系列的插值器,封装在Curves
类中,有下面13
种效果
linear
decelerate
ease
easeIn
easeOut
easeInOut
fastOutSlowIn
bounceIn
bounceOut
bounceInOut
elasticIn
elasticOut
elasticInOut
Ticker
-
Ticker
用来添加每次屏幕刷新的回调函数TickerCallback
,每次屏幕刷新都会调用。 -
类似于
Web
里面的requestAnimationFrame
方法 -
主要作用是获取每一帧刷新的通知,相当于给动画添加了一个动起来的引擎
隐式动画
-
隐式动画使用 Flutter 框架内置的动画部件创建,通过设置动画的起始值和最终值来触发。
-
当使用
setState
方法改变部件的动画属性值时,框架会自动计算出一个从旧值过渡到新值的动画 -
常用的动画部件:
AnimatedOpacity
、AnimatedContainer
、AnimatedPadding
、AnimatedPositioned
、AnimatedSwitcher
、AnimatedAlign
import 'package:flutter/material.dart';
class AnimatedContainerPage extends StatefulWidget {
AnimatedContainerPage({Key key}) : super(key: key);
@override
_AnimatedContainerPageState createState() => _AnimatedContainerPageState();
}
class _AnimatedContainerPageState extends State<AnimatedContainerPage> {
double _opacity = 1;
void _toggle() {
setState(() {
this._opacity = this._opacity > 0 ? 0 : 1;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Animated Container'),
),
body: Center(
child: AnimatedOpacity(
opacity: this._opacity,
duration: Duration(seconds: 1),
child: Container(
width: 200,
height: 200,
color: Colors.blue,
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: this._toggle,
child: Icon(Icons.play_arrow),
),
);
}
}
显式动画
-
显式动画指的是需要手动设置动画的时间,运动曲线,取值范围的动画;
-
将值传递给动画部件;最后使用一个
AnimationController
控制动画的开始和结束。 -
常用的显示动画部件:
RotationTransition
、FadeTransition
、ScaleTransition
、SizeTransition
、SlideTransition
import 'package:flutter/material.dart';
import 'dart:math';
class AnimatedContainerPage extends StatefulWidget {
AnimatedContainerPage({Key key}) : super(key: key);
@override
_AnimatedContainerPageState createState() => _AnimatedContainerPageState();
}
class _AnimatedContainerPageState extends State<AnimatedContainerPage> with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<double> _turns;
bool _playing = false;
@override
void initState() {
super.initState();
// 初始化动画控制器,设置动画时间
this._controller = new AnimationController(vsync: this, duration: Duration(seconds: 10));
// 设置动画取值范围和时间曲线
this._turns = Tween(begin: 0.0, end: pi * 2).animate(CurvedAnimation(parent: this._controller, curve: Curves.easeIn));
}
@override
void dispose() {
this._controller.dispose();
super.dispose();
}
void _toggle() {
if(this._playing) {
this._playing = false;
this._controller.stop();
} else {
this._controller.forward()..whenComplete(() => this._controller.reverse());
this._playing = true;
}
setState(() { });
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Animated Container'),
),
body: Center(
child: RotationTransition(
turns: this._turns,
child: Container(
width: 200,
height: 200,
child: Image.asset('img/fan.png', fit: BoxFit.cover),
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: this._toggle,
child: Icon(this._playing ? Icons.pause : Icons.play_arrow),
),
);
}
}
Hero动画
-
Hero
动画指的是在页面切换时一个元素从旧页面运动到新页面的动画 -
Hero
动画需要使用两个Hero
控件实现:一个用来在旧页面中,另一个在新页面。两个Hero
控件需要使用相同的tag
属性,并且不能与其他tag
重复
交织动画
-
交织动画是由一系列的小动画组成的动画。
-
每个小动画可以是连续或间断的,也可以相互重叠。
-
其关键点在于使用
Interval
部件给每个小动画设置一个时间间隔,以及为每个动画的设置一个取值范围Tween
,最后使用一个AnimationController
控制总体的动画状态。 -
Interval
继承至Curve
类,通过设置属性begin
和end
来确定这个小动画的运行范围。
class Interval extends Curve {
// 动画起始点
final double begin;
// 动画结束点
final double end;
// 动画缓动曲线
final Curve curve;
// ...
}
import 'package:flutter/material.dart';
class AnimatedContainerPage extends StatefulWidget {
AnimatedContainerPage({Key key}) : super(key: key);
@override
_AnimatedContainerPageState createState() => _AnimatedContainerPageState();
}
class _AnimatedContainerPageState extends State<AnimatedContainerPage> with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<double> _width;
Animation<double> _height;
Animation<Color> _color;
Animation<double> _border;
Animation<BorderRadius> _borderRadius;
@override
void initState() {
super.initState();
// 初始化动画控制器,设置动画时间
this._controller = new AnimationController(vsync: this, duration: Duration(seconds: 5));
// 设置动画取值范围和时间曲线
this._width = Tween<double>(begin: 100, end: 300).animate(CurvedAnimation(parent: this._controller, curve: Interval(0.0, 0.2, curve: Curves.ease)));
this._height = Tween<double>(begin: 100, end: 300).animate(CurvedAnimation(parent: this._controller, curve: Interval(0.2, 0.4, curve: Curves.ease)));
this._color = ColorTween(begin: Colors.blue, end: Colors.yellow).animate(CurvedAnimation(parent: this._controller, curve: Interval(0.4, 0.6, curve: Curves.ease)));
this._borderRadius = BorderRadiusTween(begin: BorderRadius.circular(0), end: BorderRadius.circular(150)).animate(CurvedAnimation(parent: this._controller, curve: Curves.ease));
this._border = Tween<double>(begin: 0, end: 25).animate(CurvedAnimation(parent: this._controller, curve: Interval(0.8, 1.0)));
}
@override
void dispose() {
this._controller.dispose();
super.dispose();
}
void _play() {
if(this._controller.isCompleted) {
this._controller.reverse();
} else {
this._controller.forward();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Animated Container'),
),
body: Center(
child: AnimatedBuilder(
animation: this._controller,
builder: (BuildContext context, Widget child) {
return Container(
width: this._width.value,
height: this._height.value,
decoration: BoxDecoration(
color: this._color.value,
borderRadius: this._borderRadius.value,
border: Border.all(
width: this._border.value,
color: Colors.orange
)
),
);
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: this._play,
child: Icon(Icons.refresh),
),
);
}
}
物理动画
-
物理动画是一种模拟现实世界物体运动的动画,需要建立物体的运动模型。
-
以一个物体下落为例,这个运动受到物体的下落高度,重力加速度,地面的反作用力等因素的影响
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
class AnimatedContainerPage extends StatefulWidget {
AnimatedContainerPage({Key key}) : super(key: key);
@override
_AnimatedContainerPageState createState() => _AnimatedContainerPageState();
}
class _AnimatedContainerPageState extends State<AnimatedContainerPage> with SingleTickerProviderStateMixin {
// 球心高度
double y = 70;
// Y 轴速度
double vy = -10;
// 重力
double gravity = 0.1;
// 地面反弹力
double bounce = -0.5;
// 球的半径
double radius = 50;
// 地面高度
final double height = 600;
@override
void initState() {
super.initState();
// 使用一个 Ticker 在每次更新界面时运行球体下落方法
Ticker(this._fall)..start();
}
void _fall(_) {
this.y += this.vy;
this.vy += this.gravity;
// 如果球体接触到地面,根据地面反弹力改变球体的 Y 轴速度
if(this.y + this.radius > this.height) {
this.y = this.height - this.radius;
this.vy *= this.bounce;
} else if(this.y - this.radius < 0) {
this.y = 0 + this.radius;
this.vy *= this.bounce;
}
setState(() { });
}
@override
Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width;
print(this.height);
return Scaffold(
appBar: AppBar(
title: Text('Animated Container'),
),
body: Column(
children: <Widget>[
Container(
height: this.height,
child: Stack(
children: <Widget>[
Positioned(
top: this.y - this.radius,
left: screenWidth / 2 - this.radius,
child: Container(
width: this.radius * 2,
height: this.radius * 2,
decoration: BoxDecoration(
color: Colors.blue,
shape: BoxShape.circle
),
),
),
],
),
),
Expanded(
child: Container(color: Colors.blue),
)
],
),
);
}
}
发表评论