Flutter动画学习
效果直接贴代码 运行看吧,更改push入口查看不同动画效果。
基础动画:颜色渐变、控件大小更改、位置变更
Hero动画:从一个界面push到另一个界面,有一个控件跟着动来动去。
交错动画:把基础动画结合起来,curve 0.0-1.0来控制好几个基础动画执行的先后顺序。
基础动画
动画过程可以是匀速的、加速的或者先加速后减速等。Flutter中通过Curve(曲线)来描述动画过程,Curve可以是线性的(Curves.linear),也可以是非线性的。
常用的曲线(Curve)类型
每种UI框架基本都有这些动画的基础类型
Flutter中,继承Curve类,可以自己定义不同的曲线类型。
继承关系
CurvedAnimation->Animation->Listenable
AnimationController->Animation->Listenable
AnimationController
初始化AnimationController
final AnimationController animation = new AnimationController(vsync: null)Ticker
TickerProvider创建TickerTween
默认情况下,AnimationController对象值的范围是0.0到1.0
Tween.animate()
要使用Tween对象就要调用animate()方法AnimatedWidget
animatedWidget替代下面代码
将要执行的Widget集成AnimatiedWidget
..addListener(() {
setState(() {
});
});
- AnimatedBuilder
正是将渲染逻辑分离出来 - addStatusListener()
动画状态监听
<colgroup><col style=”width: 130px;”><col style=”width: 406px;”></colgroup>
— | — |
---|---|
dismissed | 动画在起始点停止 |
forward | 动画正在正向执行 |
reverse | 动画正在反向执行 |
completed | 动画在终点停止 |
图片资源配置
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
assets:
- images/IMG_0696.jpg
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Animation Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new AnimatioStateRoute(),
);
}
}
class AnimatioStateRoute extends StatefulWidget {
@override
_AnimatioStateRouteState createState() => _AnimatioStateRouteState();
}
class _AnimatioStateRouteState extends State<AnimatioStateRoute> with SingleTickerProviderStateMixin {
Animation<double> animation; // 保存动画的插值和状态
AnimationController controller; // 控制动画 开始、暂停等
@override
void initState() {
super.initState();
// 初始化控制动画类 动画用时4秒
controller = new AnimationController(duration: const Duration(seconds: 4), vsync: this);
// 动画曲线类型
animation = CurvedAnimation(parent: controller, curve: Curves.bounceInOut);
// 动画形态变化(大小从0.0->200.0)监听
animation = new Tween(begin: 0.0, end: 200.0).animate(controller);
animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
//动画执行结束时反向执行动画
controller.reverse();
print("动画完成");
} else if (status == AnimationStatus.dismissed) {
//动画恢复到初始状态时执行动画(正向)
controller.forward();
print("初始化状态");
} else if (status == AnimationStatus.forward) {
print("动画开始");
} else if (status == AnimationStatus.reverse) {
print("动画重复");
}
});
// controller.forward();
}
void forward() {
// 开始执行动画
controller.forward();
}
void stop() {
controller.stop();
}
void reverse() {
controller.reverse();
}
@override
Widget build(BuildContext context) {
return new Scaffold(
body: new Padding(padding: new EdgeInsets.only(top: 50.0), child: new Column(
children: <Widget>[
new Center(
child: _GrowTransition(
child: new Image.asset("images/IMG_0696.jpg"),
animation: this.animation,
),
),
new Container(
child: new FlatButton(onPressed: () {
forward();
}, child: new Text("开始")),
),
new Container(
child: new FlatButton(onPressed: () {
stop();
}, child: new Text("暂停")),
),
new Container(
child: new FlatButton(onPressed: () {
reverse();
}, child: new Text("重复")),
),
new Container(
child: new FlatButton(onPressed: () {
Navigator.push(context, FadeRoute(
builder: (context){
// return HeroAnimationRoute(); // Hero动画
// return StaggerDemo(); // 交错动画
return ItemPage(mTitle: "push",);// FadeRoute push动画
}, isActive: true
)).then((value) {
print(value);
});
}, child: new Text("跳转动画")),
)
],
),
),
);
}
dispose() {
// 销毁
controller.dispose();
super.dispose();
}
}
//
class _GrowTransition extends StatelessWidget {
_GrowTransition({this.child, this.animation});
final Widget child;
final Animation<double> animation;
Widget build(BuildContext context) {
return new Center(
child: new AnimatedBuilder(
animation: animation,
builder: (BuildContext context, Widget child) {
return new Container(
height: animation.value,
width: animation.value,
child: child
);
},
child: child
),
);
}
}
class ItemPage extends StatelessWidget {
ItemPage({Key key, this.mTitle}) : super(key : key);
final String mTitle;
@override
Widget build(BuildContext context) {
return new Scaffold(
body: new Row(
children: <Widget>[
new Center(
child: new Text("ItemPage"),
),
new FlatButton(onPressed: () {
Navigator.of(context).pop('返回值');
}, child: new Text(mTitle + "-返回"))
],
)
);
}
}
// 继承PageRoute类可以实现各种转场动画
class FadeRoute extends PageRoute {
FadeRoute({
@required this.builder,
this.transitionDuration = const Duration(milliseconds: 300),
this.opaque = true,
this.barrierDismissible = false,
this.barrierColor,
this.barrierLabel,
this.maintainState = true,
this.isActive = true,
});
final WidgetBuilder builder;
@override
final Duration transitionDuration;
@override
final bool opaque;
@override
final bool barrierDismissible;
@override
final Color barrierColor;
@override
final String barrierLabel;
@override
final bool maintainState;
@override
final bool isActive;
@override
Widget buildPage(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) => builder(context);
@override
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
// 001 平移
return SlideTransition(position: new Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: const Offset(0.0, 0.0),
).animate(animation), child: builder(context),);
// 002 渐变
//当前路由被激活,是打开新路由
if(isActive) {
return FadeTransition(
opacity: animation,
child: builder(context),
);
}else{
//是返回,则不应用过渡动画
return Padding(padding: EdgeInsets.zero);
}
}
}
class HeroAnimationRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Scaffold(
body: new Container(
alignment: Alignment.topCenter,
child: InkWell(
child: Hero(
tag: "tag same", //唯一标记,前后两个路由页Hero的tag必须相同
child: ClipOval(
child: Image.asset("images/IMG_0696.jpg",
width: 50.0,
),
),
),
onTap: () {
//打开B路由
Navigator.push(context, PageRouteBuilder(
pageBuilder: (BuildContext context, Animation animation,
Animation secondaryAnimation) {
return new FadeTransition(
opacity: animation,
child: HeroAnimationRouteB(imageName: "images/IMG_0696.jpg"),
);
})
);
},
),
),
);
}
}
class HeroAnimationRouteB extends StatelessWidget {
HeroAnimationRouteB({this.imageName});
final String imageName;
@override Widget build(BuildContext context) {
return new Scaffold(
//唯一标记,前后两个路由页Hero的tag必须相同
body: new Center( child: Hero( tag: "tag same",
child: Image.asset(imageName,
width: 350.0,
)
),
),
);
}
}
// 交错动画
class StaggerAnimation extends StatelessWidget {
StaggerAnimation({ Key key, this.controller }): super(key: key){
// 动画的顺序使用curve控制 0.0-1.0
//高度动画
height = Tween<double>(
begin:.0 ,
end: 300.0,
).animate(
CurvedAnimation(
parent: controller,
curve: Interval(
0.0, 0.3, //间隔,前60%的动画时间
curve: Curves.ease,
),
),
);
// 颜色动画
color = ColorTween(
begin:Colors.green ,
end:Colors.red,
).animate(
CurvedAnimation(
parent: controller,
curve: Interval(
0.3, 0.6,//间隔,前60%的动画时间
curve: Curves.ease,
),
),
);
// 偏移动画
padding = Tween<EdgeInsets>(
begin:EdgeInsets.only(left: .0),
end:EdgeInsets.only(left: 100.0),
).animate(
CurvedAnimation(
parent: controller,
curve: Interval(
0.6, 1.0, //间隔,后40%的动画时间
curve: Curves.ease,
),
),
);
}
final Animation<double> controller;
Animation<double> height;
Animation<EdgeInsets> padding;
Animation<Color> color;
Widget _buildAnimation(BuildContext context, Widget child) {
return new Scaffold(
body: new Container(
alignment: Alignment.bottomCenter,
padding:padding.value ,
child: Container(
color: color.value,
width: 50.0,
height: height.value,
),
),
);
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
builder: _buildAnimation,
animation: controller,
);
}
}
class StaggerDemo extends StatefulWidget {
@override
_StaggerDemoState createState() => _StaggerDemoState();
}
class _StaggerDemoState extends State<StaggerDemo> with TickerProviderStateMixin {
AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 2000),
vsync: this
);
}
void _playAnimation() async {
try {
//先正向执行动画
await _controller.forward().orCancel;
//再反向执行动画
await _controller.reverse().orCancel;
} on TickerCanceled {
// the animation got canceled, probably because we were disposed
}
}
@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
_playAnimation();
},
child: Center(
child: Container(
width: 300.0,
height: 300.0,
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.1),
border: Border.all(
color: Colors.black.withOpacity(0.5),
),
),
//调用我们定义的交错动画Widget
child: StaggerAnimation(
controller: _controller
),
),
),
);
}
}