图片放大缩小
scss
class ScaleAnimationRoute extends StatefulWidget {
const ScaleAnimationRoute({Key? key}) : super(key: key);
@override
_ScaleAnimationRouteState createState() => _ScaleAnimationRouteState();
}
//需要继承TickerProvider,如果有多个AnimationController,则应该使用TickerProviderStateMixin。
class _ScaleAnimationRouteState extends State<ScaleAnimationRoute>
with SingleTickerProviderStateMixin {
late Animation<double> animation;
late AnimationController controller;
@override
initState() {
super.initState();
controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
//匀速
//图片宽高从0变到300
animation = Tween(begin: 0.0, end: 300.0).animate(controller)
..addListener(() {
setState(() => {});
});
controller.forward();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: GestureDetector(
onTap: () {
//启动动画(正向执行)
controller.reset();
controller.forward();
},
child: Image.asset(
"assets/imgs/avatar.png",
width: animation.value,
height: animation.value,
),
)),
);
}
@override
dispose() {
//路由销毁时需要释放动画资源
controller.dispose();
super.dispose();
}
}
addListener()
函数调用了setState()
,所以每次动画生成一个新的数字时,当前帧被标记为脏(dirty),这会导致widget的build()
方法再次被调用,而在build()
中,改变Image的宽高,因为它的高度和宽度现在使用的是animation.value
,所以就会逐渐放大。值得注意的是动画完成时要释放控制器(调用dispose()
方法)以防止内存泄漏。
实现点击下陷的效果
scss
class ScaleAnimationRoute extends StatefulWidget {
const ScaleAnimationRoute({Key? key}) : super(key: key);
@override
_ScaleAnimationRouteState createState() => _ScaleAnimationRouteState();
}
//需要继承TickerProvider,如果有多个AnimationController,则应该使用TickerProviderStateMixin。
class _ScaleAnimationRouteState extends State<ScaleAnimationRoute>
with SingleTickerProviderStateMixin {
late Animation<double> animation;
late AnimationController controller;
@override
initState() {
super.initState();
controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
//匀速
//图片宽高从0变到300
animation = Tween(begin: 0.0, end: 300.0).animate(controller)
..addListener(() {
setState(() => {});
});
controller.forward(from: 0.9);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
width: 330,
color: Colors.orange,
height: 180,
alignment: Alignment.center,
child: GestureDetector(
onTap: () {
//启动动画(正向执行)
controller.reset();
controller.forward(from: 0.9);
},
child: Container(
color: Colors.blue,
width: animation.value,
height: animation.value * 0.5,
),
))),
);
}
@override
dispose() {
//路由销毁时需要释放动画资源
controller.dispose();
super.dispose();
}
}
AnimatedWidget简化
通过addListener()
和setState()
来更新UI这一步其实是通用的,如果每个动画中都加这么一句是比较繁琐的。AnimatedWidget
类封装了调用setState()
的细节,并允许我们将 widget 分离出来,重构后的代码如下:
scala
class ScaleAnimationRoute1 extends StatefulWidget {
const ScaleAnimationRoute1({Key? key}) : super(key: key);
@override
_ScaleAnimationRouteState createState() => _ScaleAnimationRouteState();
}
class _ScaleAnimationRouteState extends State<ScaleAnimationRoute1>
with SingleTickerProviderStateMixin {
late Animation<double> animation;
late AnimationController controller;
@override
initState() {
super.initState();
controller =
AnimationController(duration: const Duration(seconds: 2), vsync: this);
//图片宽高从0变到300
animation = Tween(begin: 0.0, end: 300.0).animate(controller);
//启动动画
controller.forward();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
onTap: () {
controller.reset();
controller.forward();
},
child: AnimatedImage(
animation: animation,
),
));
}
@override
dispose() {
//路由销毁时需要释放动画资源
controller.dispose();
super.dispose();
}
}
class AnimatedImage extends AnimatedWidget {
const AnimatedImage({
Key? key,
required Animation<double> animation,
}) : super(key: key, listenable: animation);
@override
Widget build(BuildContext context) {
final animation = listenable as Animation<double>;
return Center(
child: Image.asset(
"assets/imgs/avatar.png",
width: animation.value,
height: animation.value,
),
);
}
}
使用AnimatedBuilder
scala
class ScaleAnimationRoute1 extends StatefulWidget {
const ScaleAnimationRoute1({Key? key}) : super(key: key);
@override
_ScaleAnimationRouteState createState() => _ScaleAnimationRouteState();
}
class _ScaleAnimationRouteState extends State<ScaleAnimationRoute1>
with SingleTickerProviderStateMixin {
late Animation<double> animation;
late AnimationController controller;
@override
initState() {
super.initState();
controller =
AnimationController(duration: const Duration(seconds: 2), vsync: this);
//图片宽高从0变到300
animation = Tween(begin: 0.0, end: 300.0).animate(controller);
//启动动画
controller.forward();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
onTap: () {
controller.reset();
controller.forward();
},
child: GrowTransition(
animation: animation,
child: Image.asset("assets/imgs/avatar.png"),
)));
}
@override
dispose() {
//路由销毁时需要释放动画资源
controller.dispose();
super.dispose();
}
}
class GrowTransition extends StatelessWidget {
const GrowTransition({
Key? key,
required this.animation,
this.child,
}) : super(key: key);
final Widget? child;
final Animation<double> animation;
@override
Widget build(BuildContext context) {
return Center(
child: AnimatedBuilder(
animation: animation,
builder: (BuildContext context, child) {
return SizedBox(
height: animation.value,
width: animation.value,
child: child,
);
},
child: child,
),
);
}
}
用AnimatedWidget 可以从动画中分离出 widget,而动画的渲染过程(即设置宽高)仍然在AnimatedWidget 中,假设如果我们再添加一个 widget 透明度变化的动画,那么我们需要再实现一个AnimatedWidget,这样不是很优雅,如果我们能把渲染过程也抽象出来,那就会好很多,而AnimatedBuilder正是将渲染逻辑分离出来。
上面的代码中有一个迷惑的问题是,child
看起来像被指定了两次。但实际发生的事情是:将外部引用child
传递给AnimatedBuilder
后,AnimatedBuilder
再将其传递给匿名构造器, 然后将该对象用作其子对象。最终的结果是AnimatedBuilder
返回的对象插入到 widget 树中。
其实它会带来三个好处:
- 不用显式的去添加帧监听器,然后再调用
setState()
了,这个好处和AnimatedWidget
是一样的。 - 更好的性能:因为动画每一帧需要构建的 widget 的范围缩小了,如果没有
builder
,setState()
将会在父组件上下文中调用,这将会导致父组件的build
方法重新调用;而有了builder
之后,只会导致动画widget自身的build
重新调用,避免不必要的rebuild。 - 通过
AnimatedBuilder
可以封装常见的过渡效果来复用动画。通过封装一个GrowTransition
来说明。
FadeTransition 实现透明度变化效果
Flutter中正是通过这种方式封装了很多动画,如:FadeTransition、ScaleTransition、SizeTransition
等,很多时候都可以复用这些预置的过渡类。
scala
class FadeTranstionScreen extends StatefulWidget {
FadeTranstionScreen({Key? key}) : super(key: key);
@override
_FadeTranstionScreenState createState() => _FadeTranstionScreenState();
}
class _FadeTranstionScreenState extends State<FadeTranstionScreen>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller =
AnimationController(vsync: this, duration: const Duration(seconds: 2));
_animation = Tween(begin: 0.0, end: 1.0).animate(_controller);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
_controller.forward();
return Scaffold(
appBar: AppBar(title: const Text("FadeTranstion")),
body: GestureDetector(
onTap: () {
_controller.reset();
_controller.forward();
},
child: Container(
alignment: Alignment.center,
child: FadeTransition(
opacity: _animation,
child: Container(
width: 100,
height: 100,
color: Colors.blue,
),
),
),
));
}
}
ScaleTransition 实现缩小放大效果
scala
class FadeTranstionScreen extends StatefulWidget {
FadeTranstionScreen({Key? key}) : super(key: key);
@override
_FadeTranstionScreenState createState() => _FadeTranstionScreenState();
}
class _FadeTranstionScreenState extends State<FadeTranstionScreen>
with SingleTickerProviderStateMixin {
late final AnimationController _controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..repeat(reverse: true);
late final Animation<double> _animation = CurvedAnimation(
parent: _controller,
curve: Curves.fastOutSlowIn,
);
@override
void dispose() {
super.dispose();
_controller.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: GestureDetector(
onTap: () {
_controller.reset();
_controller.forward();
},
child: ScaleTransition(
scale: _animation,
child: const Padding(
padding: EdgeInsets.all(8.0),
child: FlutterLogo(size: 150.0),
),
),
)),
);
}
}
SizeTransition 实现文字横向飞入效果
scala
class DemoSizeTransition extends StatefulWidget {
const DemoSizeTransition({super.key});
@override
_DemoSizeTransitionState createState() => _DemoSizeTransitionState();
}
class _DemoSizeTransitionState extends State<DemoSizeTransition>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
@override
void initState() {
super.initState();
_animationController =
AnimationController(vsync: this, duration: const Duration(seconds: 1));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("SizeTransition")),
body: SizeTransition(
//偏移量
axisAlignment: 0.0,
//动画控制
sizeFactor: _animationController,
axis: Axis.horizontal,
child: buildContainer(),
),
floatingActionButton: FloatingActionButton(
child: const Text("开始"),
onPressed: () {
_animationController.forward(from: 0);
},
),
);
}
Container buildContainer() {
return Container(
//子Widget 对齐方式
alignment: Alignment.center,
width: 300,
//高度
height: 200,
//背景颜色
color: Colors.blue,
child: const Text(
"此处文字需要有一些动画效果",
style: TextStyle(color: Colors.white),
),
);
}
}