动画组件
有一些现成的动画组件,只要设置时长,对应变化的属性值就可以了,比较方便。
image.png
import 'package:flutter/material.dart';
class AnimatedDemo extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _AnimatedDemoState();
}
}
class _AnimatedDemoState extends State<AnimatedDemo> {
double _padding = 10;
Alignment _align = Alignment.topRight;
double _height = 100;
double _left = 0;
Color _color = Colors.red;
TextStyle _style = TextStyle(color: Colors.black);
@override
Widget build(BuildContext context) {
final duration = Duration(seconds: 1);
return Scaffold(
appBar: AppBar(
title: Text('动画组件的例子'),
),
body: SingleChildScrollView(
child: Column(
children: <Widget>[
RaisedButton(
onPressed: () {
setState(() {
_padding = 20;
});
},
child: AnimatedPadding(
duration: duration,
padding: EdgeInsets.all(_padding),
child: Text("AnimatedPadding"),
),
),
SizedBox(
height: 50,
child: Stack(
children: <Widget>[
AnimatedPositioned(
duration: duration,
left: _left,
child: RaisedButton(
onPressed: () {
setState(() {
_left = 100;
});
},
child: Text("AnimatedPositioned"),
),
)
],
),
),
Container(
height: 100,
color: Colors.grey,
child: AnimatedAlign(
duration: duration,
alignment: _align,
child: RaisedButton(
onPressed: () {
setState(() {
_align = Alignment.center;
});
},
child: Text("AnimatedAlign"),
),
),
),
AnimatedContainer(
duration: duration,
height: _height,
color: _color,
child: FlatButton(
onPressed: () {
setState(() {
_height = 150;
_color = Colors.blue;
});
},
child: Text(
"AnimatedContainer",
style: TextStyle(color: Colors.white),
),
),
),
AnimatedDefaultTextStyle(
child: GestureDetector(
child: Text("hello world"),
onTap: () {
setState(() {
_style = TextStyle(
color: Colors.blue,
decorationStyle: TextDecorationStyle.solid,
decorationColor: Colors.blue,
);
});
},
),
style: _style,
duration: duration,
),
RaisedButton(onPressed: (){
setState(() {
_padding = 10;
_align = Alignment.topRight;
_height = 100;
_left = 0;
_color = Colors.red;
_style = TextStyle(color: Colors.black);
});
}, child: Text('参数还原'),),
RaisedButton(onPressed: (){
setState(() {
_padding = 20;
_align = Alignment.center;
_height = 150;
_left = 100;
_color = Colors.blue;
_style = TextStyle(color: Colors.blue,
decorationStyle: TextDecorationStyle.solid,
decorationColor: Colors.blue,
);
});
}, child: Text('一键设置'),),
].map((e){
return Padding(padding: EdgeInsets.all(16), child: e);
}).toList(),
),
),
);
}
}
image.png
过渡动画
采用
Navigator.of(context).push(PageRouteBuilder())
进行跳转,不能用Navigator.of(context).pushNamed('name');
动画方式有现成的可用,比如
FadeTransition
就是指“渐隐渐入过渡”两个页面之间有耦合,页面A中需要引入并创建页面B
页面A;
import 'package:flutter/material.dart';
import './push_b.dart';
class PushA extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('过渡动画页面A'),
),
body: Center(
child: Column(
children: <Widget>[
RaisedButton(
onPressed: () {
Navigator.of(context).push(PageRouteBuilder(
transitionDuration: Duration(seconds: 1),
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return ScaleTransition(
scale: animation,
child: PushB(),
);
},
));
},
child: Text('Scale方式跳转'),
),
RaisedButton(
onPressed: () {
Navigator.of(context).push(PageRouteBuilder(
transitionDuration: Duration(seconds: 1),
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return FadeTransition(
opacity: animation,
child: PushB(),
);
},
));
},
child: Text('Fade方式跳转'),
),
RaisedButton(
onPressed: () {
Navigator.of(context).push(PageRouteBuilder(
transitionDuration: Duration(seconds: 1),
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return SizeTransition(
sizeFactor: animation,
child: PushB(),
);
},
));
},
child: Text('Size方式跳转'),
),
RaisedButton(
onPressed: () {
Navigator.of(context).push(PageRouteBuilder(
transitionDuration: Duration(seconds: 1),
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return RotationTransition(
turns: animation,
child: PushB(),
);
},
));
},
child: Text('Rotation方式跳转'),
),
].map((e) {
return Padding(
padding: EdgeInsets.all(16),
child: e,
);
}).toList(),
),
),
);
}
}
- 页面B
import 'package:flutter/material.dart';
class PushB extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('过渡动画页面B'),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('返回过渡动画页面A'),
),
),
);
}
}
image.png
飞行动画
和过渡动画差不都,只是页面A和页面B都有一个Hero()
组件,并且要求tag
属性保持一致
import 'package:flutter/material.dart';
import './hero_b.dart';
class HeroA extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('飞行动画A'),
),
body: Center(
child: Container(
width: 300,
height: 300,
child: InkWell(
child: Column(
children: <Widget>[
Hero(
tag: 'icon-60',
child: ClipOval(
child: Image.asset('image/icon-60.png'),
),
),
Padding(
padding: EdgeInsets.only(top: 10),
child: Text('点击看大图'),
),
],
),
onTap: () {
Navigator.of(context).push(PageRouteBuilder(pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return FadeTransition(
opacity: animation,
child: HeroB(),
);
}, transitionDuration: Duration(seconds: 1)));
},
),
),
),
);
}
}
import 'package:flutter/material.dart';
class HeroB extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('看大图'),
),
body: Center(
child: GestureDetector(
child: Hero(
tag: 'icon-60', //唯一标记,前后两个路由页Hero的tag必须相同
child: Image.asset(
'image/icon-60.png',
width: 300,
height: 300,
fit: BoxFit.fill,
),
),
onTap: () {
Navigator.of(context).pop();
},
),
),
);
}
}
image.png
image.png
- 虽然也是两个页面跳转,但是看起来,就像是在同一个页面一样。
基本动画
动画需要记忆动画参数,所以要使用
StatefulWidget
AnimationController
设置动画时长,控制动画的执行方式;CurvedAnimation
设置动画的轨迹,需要一个AnimationController
参数;Tween
设置属性的开始和结束的值,调用animate
函数转换为动画类型Animation<T>
。需要一个参数,类型可以是AnimationController
,或者是CurvedAnimation
。动画的状态可以通过
Animation<T>
的addStatusListener
方法监听。
import 'package:flutter/material.dart';
class Circle extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _CircleState();
}
}
class _CircleState extends State<Circle> with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<double> _withHeight;
@override
void initState() {
super.initState();
// 时长
_controller = AnimationController(
duration: Duration(seconds: 3),
vsync: this,
);
// 动画曲线
final _curve = CurvedAnimation(parent: _controller, curve: Curves.easeIn);
// 图片宽高从0变到300
_withHeight = Tween(begin: 0.0, end: 300.0).animate(_curve);
// 状态侦听
_withHeight.addStatusListener((status) {
if (status == AnimationStatus.completed) {
// 动画执行结束时反向执行动画
_controller.reverse();
} else if (status == AnimationStatus.dismissed) {
// 动画反向执行结束时正向执行动画
_controller.forward();
}
});
// 启动动画(正向执行)
_controller.forward();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('循环动画的例子'),
),
body: SizedBoxAnimation(
child: Image.asset('image/icon-60.png', fit: BoxFit.fill,),
animation: _withHeight),
);
}
@override
void dispose() {
// 路由销毁时需要释放动画资源
_controller.dispose();
super.dispose();
}
}
class SizedBoxAnimation extends StatelessWidget {
SizedBoxAnimation({@required this.child, @required this.animation});
final Widget child;
final Animation<double> animation;
@override
Widget build(BuildContext context) {
return Center(
child: AnimatedBuilder(
child: child,
animation: animation,
builder: (BuildContext context, Widget child) {
return SizedBox(
width: animation.value,
height: animation.value,
child: child,
);
},
),
);
}
}
image.png