Animation
-
Animation是
Flutter 动画库中的核心类,插入用于指导动画的值。 -
Animation
对象知道动画目前的状态(例如,是否开始,暂停,前进或倒退),但是对屏幕上显示的内容一无所知。 -
Animation
对象与渲染或build()
函数无关。 -
Animation
对象具有状态。它的当前值在.value
中始终可用。 -
动画还可以插入除 double 以外的类型,比如
Animation<Color>
或者Animation<Size>
。
AnimationController
AnimationController
是动画控制器,是一个特殊的 Animation
对象,派生自Animation<double>,
管理着 Animation,
所以可以用在任何需要 Animation
对象的地方。
AnimationController
用于控制动画,它包含动画的启动forward()
、停止stop()
、反向播放 reverse()
等方法。
AnimationController({
double? value,
this.duration,
this.reverseDuration,
this.debugLabel,
this.lowerBound = 0.0,
this.upperBound = 1.0,
this.animationBehavior = AnimationBehavior.normal,
required TickerProvider vsync,
}) : assert(lowerBound != null),
assert(upperBound != null),
assert(upperBound >= lowerBound),
assert(vsync != null),
_direction = _AnimationDirection.forward {
_ticker = vsync.createTicker(_tick);
_internalSetValue(value ?? lowerBound);
}
duration
表示动画执行的时长,通过它我们可以控制动画的速度。vsync:
当创建一个AnimationController
时必须要传递一个vsync
参数,可以防止屏幕外动画消耗不必要资源。单个AnimationController
使用SingleTickerProviderStateMixin
,多个AnimationController
使用TickerProviderStateMixin
创建 AnimationController
的同时,也赋予了一个 vsync
参数。 vsync
的存在防止后台动画消耗不必要的资源。
AnimationController
的初始化常常在 statefulWidget 中进行。
AnimationController.value
就是当前动画的值。
- Play an animation forward or in reverse, or stop an animation.()
- Set the animation to a specific value.
- Define the upperBound and lowerBound values of an animation.
- Create a fling animation effect using a physics simulation.
CurvedAnimation
CurvedAnimation 动画执行曲线是Animation的一个实现类,它的目的是为了给AnimationController增加动画曲线。
- 动画过程可以是匀速的、匀加速的或者先加速后减速等。
- Flutter中通过
Curve
(曲线)来描述动画过程,我们把匀速动画称为线性的(Curves.linear),而非匀速动画称为非线性的。 CurvedAnimation
可以通过包装AnimationController
和Curve
生成一个新的动画对象
CurvedAnimation({
required this.parent,
required this.curve,
this.reverseCurve,
}) : assert(parent != null),
assert(curve != null) {
_updateCurveDirection(parent.status);
parent.addStatusListener(_updateCurveDirection);
}
/// The animation to which this animation applies a curve.
@override
final Animation<double> parent;
animationConroller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 50), //变化时间
lowerBound: 0.0, //被忽略值
upperBound: 1.0, //已完成值
);
animation = CurvedAnimation(parent: animationConroller, curve: Curves.bounceIn);
Tween
Tween 插值器,这是为动画提供起始值和结束值的一个对象。在flutter中,动画其实就是从起始值到结束值的一个过渡过程。
Tween
为动画对象插入一个范围值。Tween
对象不存储任何状态。而是提供evaluate(Animation<double> animation)
方法,将映射函数应用于动画当前值。Animation
对象的当前值可以在.value
方法中找到。 evaluate 函数还执行一些内部处理内容,比如确保当动画值在 0.0 和1.0 时分别返回起始点和终点。- 要使用
Tween
对象仅仅时映射,动画的控制依旧是在AnimationController
控制。因此需要Tween
调用animate()
传入控制器对象。 - 默认情况下,Flutter 会将起始值和结束值分别定义为double类型的0.0和1.0.
除了通过泛型自定义插值类型之外,Flutter还提供了很多具体类型的插值器。如:ColorTween
.animate()
方法会返回一个 Animation,而不是 Animatable。
animation = Tween<double>(begin: 390.0, end: 400.0).animate(animationConroller);
动画通知
一个 Animation 对象可以有多个 Listener
和 StatusListener
,用 addListener()和
addStatusListener()
来定义。
addListener()
;它可以用于给Animation
添加帧监听器,在每一帧都会被调用。帧监听器中最常见的行为是改变状态后调用setState()
来触发UI重建。addStatusListener()
;它可以给Animation
添加“动画状态改变”监听器;动画开始、结束、正向或反向。
动画的状态
dimissed:动画停在开始处
completed:动画停在结束出
forward:动画正在从开始处运行到结束处(正向)
reverse:动画正在从结束出运行到开始处(反向)
动画的控制方法
- forward:正向执行动画
- reverse:反向执行动画
- repeat:反复执行动画
- reset:重置动画
forward和reverse方法中都有from参数,表示动画从 from 值开始执行,而不是从lowerBound到upperBound.
实例:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class AnimationPage extends StatefulWidget {
@override
State<AnimationPage> createState() => _AnimationPageState();
}
class _AnimationPageState extends State<AnimationPage> with SingleTickerProviderStateMixin {
late AnimationController animationConroller;
late Animation<double> animation;
@override
void initState() {
super.initState();
animationConroller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 50), //变化时间
lowerBound: 0.0, //被忽略值
upperBound: 1.0, //已完成值
);
animation = CurvedAnimation(parent: animationConroller, curve: Curves.bounceIn);
animation = Tween(begin: 390.0, end: 400.0).animate(animation);
animation.addListener(() {
setState(()=>{});
});
animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
//当动画完成时
animationConroller.reverse();
} else if (status == AnimationStatus.dismissed) {
//当动画未完成时,继续向前执行
animationConroller.forward();
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("动画"),
),
body: Center(
child: Container(
child: Icon(
Icons.favorite,
color: Colors.red,
size: animation.value,
),
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: () {
print("执行动画");
//_controller.forward();//向前执行动画
/*点击按钮暂停和开始*/
if (animationConroller.isAnimating) //如果正在执行动画
animationConroller.stop(); //则暂停
else
animationConroller.forward();
},
),
);
}
}
个人理解:
AnimationController
是控制动画的核心,它定义了动画的执行时间、起始值和结束值等属性。
后面的Animation
对象则是用来描述动画的变化过程,例如动画的曲线、插值方式、目标值等。
这些Animation
对象都是基于AnimationController
的,它们继承了AnimationController
的控制,同时添加了更多对动画的描述。
使用AnimatedWidget简化
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
//使用AnimatedWidget简化
class AnimationWithWidget extends StatefulWidget {
const AnimationWithWidget({Key? key}) : super(key: key);
@override
State<AnimationWithWidget> createState() => _AnimationWithWidgetState();
}
class _AnimationWithWidgetState extends State<AnimationWithWidget> with SingleTickerProviderStateMixin{
late AnimationController animationConroller;
late Animation<double> animation;
@override
void initState() {
super.initState();
animationConroller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 50), //变化时间
lowerBound: 0.0, //被忽略值
upperBound: 1.0, //已完成值
);
animation = CurvedAnimation(parent: animationConroller, curve: Curves.bounceIn);
animation = Tween(begin: 390.0, end: 400.0).animate(animation);
animation.addListener(() {
setState(()=>{});
});
animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
//当动画完成时
animationConroller.reverse();
} else if (status == AnimationStatus.dismissed) {
//当动画未完成时,继续向前执行
animationConroller.forward();
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("动画_使用AnimatedWidget简化"),
),
body:animationOne(animation:animation ,) ,
floatingActionButton: FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: () {
print("执行动画");
//_controller.forward();//向前执行动画
/*点击按钮暂停和开始*/
if (animationConroller.isAnimating) //如果正在执行动画
animationConroller.stop(); //则暂停
else
animationConroller.forward();
},
),
);
}
}
class animationOne extends AnimatedWidget{
const animationOne({super.key, required Animation<double> animation})
: super(listenable: animation);
@override
Widget build(BuildContext context) {
final animation = listenable as Animation<double>;
return Center(
child: Container(
child: Icon(
Icons.favorite,
color: Colors.red,
size: animation.value,
),
),
);
}
}
用AnimatedBuilder重构
将渲染逻辑分离出来
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class AnimationWithBuilder extends StatefulWidget {
@override
State<AnimationWithBuilder> createState() => _State();
}
class _State extends State<AnimationWithBuilder> with SingleTickerProviderStateMixin {
late AnimationController animationConroller;
late Animation<double> animation;
@override
void initState() {
super.initState();
animationConroller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 50), //变化时间
lowerBound: 0.0, //被忽略值
upperBound: 1.0, //已完成值
);
animation = CurvedAnimation(parent: animationConroller, curve: Curves.bounceIn);
animation = Tween(begin: 390.0, end: 400.0).animate(animation);
animation.addListener(() {
setState(()=>{});
});
animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
//当动画完成时
animationConroller.reverse();
} else if (status == AnimationStatus.dismissed) {
//当动画未完成时,继续向前执行
animationConroller.forward();
}
});
}
@override
void dispose() {
animationConroller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("动画_使用AnimatedBuilder简化"),
),
body:BuilderAnimation(animation) ,
floatingActionButton: FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: () {
print("执行动画");
/*点击按钮暂停和开始*/
if (animationConroller.isAnimating) //如果正在执行动画
animationConroller.stop(); //则暂停
else
animationConroller.forward();
},
),
);
}
}
class BuilderAnimation extends StatelessWidget {
final Animation<double> animation;
BuilderAnimation(this.animation);
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: animation,
child:Icon(
Icons.favorite,
color: Colors.red,
size: animation.value,
) ,
builder: (context,child){
return child!;
});
}
}
Hero动画
在 Flutter 中,图像从当前页面转到另一个页面称为 hero 动画,相同的动作有时也被称为 共享元素过渡。
const Hero({
Key? key,
required this.tag,
this.createRectTween,
this.flightShuttleBuilder,
this.placeholderBuilder,
this.transitionOnUserGestures = false,
required this.child,
}) : assert(tag != null),
assert(transitionOnUserGestures != null),
assert(child != null),
super(key: key);
- tag, 在不同页面分别使用两个 hero widgets,同时使用配对的标签来实现动画。
-
通过推送目标页面到 Navigator 堆栈来触发动画。 Navigator 推送并弹出操作触发原页面和目标页面中含有配对标签 heroes 的 hero 动画。
-
Flutter 设置了 tween 用来界定 Hero 从起点到终点的界限(插入大小和位置),并在图层上执行动画。
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart' show timeDilation;
//跳转后的照片类集合
class Photo extends StatelessWidget {
const Photo({super.key, required this.photo, this.onTap});
final String photo;
final VoidCallback? onTap;
@override
Widget build(BuildContext context) {
return Material(
// Slightly opaque color appears where the image has transparency.
color: Theme.of(context).primaryColor.withOpacity(0.25),
child: InkWell(
onTap: onTap,
child: LayoutBuilder(
builder: (context, size) {
return Image.asset(
photo,
fit: BoxFit.contain,
);
},
),
),
);
}
}
class RadialExpansion extends StatelessWidget {
const RadialExpansion({
super.key,
required this.maxRadius,
this.child,
}) : clipRectSize = 2.0 * (maxRadius / math.sqrt2);
final double maxRadius;
final double clipRectSize;
final Widget? child;
@override
Widget build(BuildContext context) {
return ClipOval(
child: Center(
child: SizedBox(
width: clipRectSize,
height: clipRectSize,
child: ClipRect(
child: child,
),
),
),
);
}
}
class RadialExpansionDemo extends StatelessWidget {
const RadialExpansionDemo({super.key});
static double kMinRadius = 32.0;
static double kMaxRadius = 128.0;
static Interval opacityCurve =
const Interval(0.0, 0.75, curve: Curves.fastOutSlowIn);
//Rect.fromLTRB(10, 20, 20, 10)
static RectTween _createRectTween(Rect? begin, Rect? end) {
return MaterialRectCenterArcTween(begin: begin, end: end);
}
@override
Widget build(BuildContext context) {
timeDilation = 5.0; // 1.0 is normal animation speed.
return Scaffold(
appBar: AppBar(
title: const Text('Radial Transition Demo'),
),
body: Container(
padding: const EdgeInsets.all(32),
alignment: FractionalOffset.bottomLeft,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_buildHero(context, 'assets/tabbar/group_active.png', 'Chair'),
_buildHero(context, 'assets/tabbar/home_active.png', 'Binoculars'),
_buildHero(context, 'assets/tabbar/mall_active.png', 'Beach ball'),
],
),
),
);
}
// 底部导航栏
Widget _buildHero(BuildContext context, String imageName, String description) {
return SizedBox(
width: kMinRadius * 2.0,
height: kMinRadius * 2.0,
child: Hero(
createRectTween: _createRectTween,
tag: imageName,
child: RadialExpansion(
maxRadius: kMaxRadius,
child: Photo(
photo: imageName,
onTap: () {
Navigator.of(context).push(
PageRouteBuilder<void>(
pageBuilder: (context, animation, secondaryAnimation) {
return AnimatedBuilder(
animation: animation,
builder: (context, child) {
return Opacity(
opacity: opacityCurve.transform(animation.value),
child: _buildPage(context, imageName, description),
);
});
},
),
);
},
),
),
),
);
}
//点击导航栏item后跳转的页面
static Widget _buildPage(BuildContext context, String imageName, String description) {
return Container(
color: Theme.of(context).canvasColor,
child: Center(
child: Card(
elevation: 8,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: kMaxRadius * 2.0,
height: kMaxRadius * 2.0,
child: Hero(
createRectTween: _createRectTween,
tag: imageName,
child: RadialExpansion(
maxRadius: kMaxRadius,
child: Photo(
photo: imageName,
onTap: () {
Navigator.of(context).pop();
},
),
),
),
),
Text(
description,
style: const TextStyle(fontWeight: FontWeight.bold),
textScaleFactor: 3,
),
const SizedBox(height: 16),
],
),
),
),
);
}
}
/*
void main() {
runApp(
const MaterialApp(
home: RadialExpansionDemo(),
),
);
}*/