flutter 动画
本文最后更新于:2022年4月22日 上午
flutter 动画
基本使用
前置条件
- 必须是
StatefulWidget
且混入SingleTickerProviderStateMixin
- 创建 动画
AnimationController
,传入vsync: this
- 创建 动画速率
CurvedAnimation
- 创建过渡值
Tween
- 开启动画
- 监听动画值改变
动画创建比较麻烦,如果项目中确实有用到动画,可以进行一定的封装
animationController.forward()
开启animationController.stop()
停止animationController.reverse()
反转animationController.addStatusListener()
监听动画状态改变animationController.status
动画当前状态animationController.dispose()
销毁动画
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@override
State<HomePage> createState() => _HomePageState();
}
/*
动画 前置条件
1. StatefulWidget
2. 混入 SingleTickerProviderStateMixin
*/
class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
AnimationController? animationController;
CurvedAnimation? curvedAnimation;
Animation<double>? tween;
@override
void initState() {
super.initState();
// 1. 创建 动画 controller
animationController = AnimationController(vsync: this, duration: const Duration(seconds: 1));
// 2. 创建动画速率
curvedAnimation = CurvedAnimation(parent: animationController!, curve: Curves.linear);
// 3. 创建动画过渡值
tween = Tween(begin: 50.0, end: 100.0).animate(curvedAnimation);
// 4.1 开启动画
animationController.forward();
// 4.2 监听动画改变
animationController.addListener(() {
setState(() {});
});
// 4.3 监听动画状态改变
animationController.addStatusListener((status) {
switch (status) {
// 监听动画完成
case AnimationStatus.completed:
animationController.reverse();
break;
// 监听动画停止
case AnimationStatus.dismissed:
animationController.forward();
break;
default:
}
});
}
@override
void dispose() {
super.dispose();
animationController.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Listener(
onPointerUp: (e) {
// 点击时 动画停止,再次点击动画继续
if (animationController.isAnimating) {
animationController.stop();
} else {
if (animationController.status == AnimationStatus.forward)
animationController.forward();
else
animationController.reverse();
}
},
child: Icon(
Icons.favorite,
color: Colors.red[400],
size: tween.value, // 使用动画变化的值
),
),
),
);
}
}
优化
继承 AnimatedWidget
将需要执行动画的 widget
抽离且继承自 AnimatedWidget
// 抽离动画widget,当值发生改变时重新构建 widget
class IconAnimation extends AnimatedWidget {
const IconAnimation(Animation anim) : super(listenable: anim);
Animation<double> get _size => listenable as Animation<double>;
@override
Widget build(BuildContext context) {
return Icon(
Icons.favorite,
color: Colors.red[400],
size: _size.value, // 使用动画变化的值
);
}
}
// 使用
child: IconAnimation(tween),
AnimatedBuilder
在需要执行动画的组件外包裹一层 AnimatedBuilder
,在动画改变的时候可以通过缓存 child
的方式优化(该代码未展示,使用方式和其它的缓存 child
的方式类似)
child: AnimatedBuilder(
animation: animationController,
builder: (ctx, child) {
return Icon(
Icons.favorite,
color: Colors.red[400],
size: tween.value, // 使用动画变化的值
);
},
)
交织动画
多个动画同时执行
如下,在动画变大变小的同时加入透明度的动画
可以在创建 Tween
的时候创建多个不同的 Tween
import 'package:flutter/animation.dart';
import 'package:flutter/material.dart';
main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@override
State<HomePage> createState() => _HomePageState();
}
/*
动画 前置条件
1. StatefulWidget
2. 混入 SingleTickerProviderStateMixin
*/
class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
late AnimationController animationController;
late CurvedAnimation curvedAnimation;
late Animation<double> tween;
late Animation<double> opcityTween; // 透明度
@override
void initState() {
super.initState();
// 1. 创建 动画 controller
animationController = AnimationController(vsync: this, duration: Duration(seconds: 1));
// 2. 创建动画速率
curvedAnimation = CurvedAnimation(parent: animationController, curve: Curves.linear);
// 3. 创建动画过渡值
tween = Tween(begin: 50.0, end: 100.0).animate(curvedAnimation);
// TODO: 创建透明度
opcityTween = Tween(begin: 0.0, end: 1.0).animate(curvedAnimation);
// 4.1 开启动画
animationController.forward();
// 4.2 监听动画改变
animationController.addListener(() {
setState(() {});
});
// 4.3 监听动画状态改变
animationController.addStatusListener((status) {
switch (status) {
// 监听动画完成
case AnimationStatus.completed:
animationController.reverse();
break;
// 监听动画停止
case AnimationStatus.dismissed:
animationController.forward();
break;
default:
}
});
}
@override
void dispose() {
super.dispose();
animationController.dispose();
}
@override
Widget build(BuildContext context) {
print('执行build 方法');
return Scaffold(
appBar: AppBar(title: Text('交织动画 (多个动画同时执行)')),
body: Center(
child: AnimatedBuilder(
animation: animationController,
builder: (ctx, child) {
return Opacity(
opacity: opcityTween.value,
child: Icon(
Icons.favorite,
color: Colors.red[400],
size: tween.value, // 使用动画变化的值
),
);
},
),
),
);
}
}
Hero 动画
Hero
动画为动一个页面跳转到另一个页面时,同一个图片或者widget
的过渡缩放动画,如:从首页的商品列表点击封面图进入详情页时的封面图过渡动画。在前端,这个过渡动画会比较难处理,但在 flutter
中现起来就很简单
使用方式及其简单,在需要连接的动画外套一个 Hero
且传入一致的 tag
,即可保证路由跳转的时候会有个缩放效果的动画(把路由的动画同时设置为渐变效果更佳)
import 'package:flutter/material.dart';
main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: HomePage(),
routes: {
'/home': (ctx) => HomePage(),
// '/detail': (ctx) => DetailPage(),
},
initialRoute: '/home',
);
}
}
// 首页
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Hero 动画 首页')),
body: GridView.builder(
itemCount: 20,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, mainAxisSpacing: 5, crossAxisSpacing: 5, childAspectRatio: 16 / 9),
itemBuilder: (ctx, index) {
String url = 'https://picsum.photos/500/500?random=$index';
return GestureDetector(
onTap: () {
Navigator.of(context).push(
// 路由过渡动画
PageRouteBuilder(
pageBuilder: (ctx, anim1, anim2) => FadeTransition(opacity: anim1, child: DetailPage(url: url)),
),
);
},
// 加一层 Hero
child: Hero(tag: url, child: Image.network(url, height: 50,fit: BoxFit.cover)),
);
},
),
// body: Text('home'),
);
}
}
class DetailPage extends StatelessWidget {
final String url;
const DetailPage({Key? key, required this.url}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Hero 动画 详情页')),
// 加一层 Hero
body: Hero(tag: url, child: Image.network(url)),
);
}
}
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议,转载请注明出处。