本节学习老孟《Flutter 实战入门》。问题:为什么我代码的动画,没有一个时动起来,问题出现哪里。
动画实现得基本原理是:将一定顺序得YUI界面连续显示出来,借助人得视觉暂留现象,从而达到连续运动得效果。动画与电影得原理是一样得,动画系统中一个重要得指标是帧率fps(Frame Per Second),即每秒得帧数。对于人眼来说,帧率超过24PS就会感觉比较顺滑;而Flutter中,理论上可以实现60FPS。
一、动画简介
1、Animation
Animation是一个抽象(abstract)类,它不能直接实例化,比较常用得Animation类是Animation。Animation对象再一段时间内生成一个区间值。Animation得输出可以是线性得,也可以是非线性得,因此Animation本身和UI渲染没有任何关系,它拥有动画当前值和状态。通过给Animation对象添加监听器,可以监听动画得每一帧及动画状态。添加监听得方法有:
addListener():每一帧都会调用,一般会在其中调用setState()来触发UI重建;
addStatusListener():添加动画状态改变监听器,当动画状态开始
结束、正向、反向发生变化时调用。
2、Curve
动画得过程时线性得还是非线性得是由Curve确定得,Curve得作用和android中得Interpolator(插值)时一样得,负责控制动画变化得速率,通俗地讲就是使动画得效果能够以匀速、加速、抛物线等各种速率变化。
使用CurvedAnimation来指定动画曲线,代码如下:
animation = CurvedAnimation(parent: animationController, curve: Curves.bounceIn),
3、AnmationController
AnmationController使动画控制器,控制动画得播放、停止等。AnmationController继承自Animation,是一个特殊得Animation对象,屏幕刷新得每一帧都会生成一个新得值,默认情况下它会线性地生成0.0~1.0的值。
AnimationController animationController;
animationController = AnimationController(
lowerBound: -widget.actionsWidth,
upperBound: 0,
vsync: this,
duration: Duration(milliseconds: 300)
)
4、Tween
AnimationController继承自Anmation,输出的值只能为double类型。如果要求动画的效果是颜色变化,这时候AnimationController并不能满足需求,而Tween继承自Animatable,它提供了evaluate方法以获取当前映射值。使用Tween对象需要调用animate方法传入控制器对象,并返回一个animation。
//从白色渐变到黑色
ColorTween(begin: Colors.white,end: Colors.black);
//控件大小变化
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: AnimationDemo(),
);
}
}
class AnimationDemo extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _AnimationDemo();
}
}
class _AnimationDemo extends State<AnimationDemo> with SingleTickerProviderStateMixin{
AnimationController animationController;
Animation<double> animation;
@override
void initState() {
super.initState();
animationController = AnimationController(
duration: Duration(seconds: 1),
lowerBound: 0.0,
upperBound: 1.0,
vsync: this,
)..addStatusListener((status) {
if (status == AnimationStatus.completed) {
//执行结束反向执行
animationController.reverse();
}else if (status == AnimationStatus.dismissed) {
//反向执行结束正向执行
animationController.forward();
}
});
animation = CurvedAnimation(parent: animationController, curve: Curves.easeIn);
animation = Tween(begin: 80.0,end: 200.0,).animate(animationController);
animationController.forward();
}
@override
Widget build(BuildContext context) {
return Center(
child: Container(
height: animation.value,
width: animation.value,
color: Colors.green,
),
);
throw UnimplementedError();
}
@override
void dispose() {
super.dispose();
animationController.dispose();
}
}
三、AnimatedWidget
AnimatedWidget 封装了setState()方法部分。修改后代码:
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: AnimationDemo(),
);
}
}
class AnimationDemo extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _AnimationDemo();
}
}
class _AnimationDemo extends State<AnimationDemo> with SingleTickerProviderStateMixin{
AnimationController animationController;
Animation<double> animation;
@override
void initState() {
super.initState();
animationController = AnimationController(
duration: Duration(seconds: 1),
lowerBound: 0.0,
upperBound: 1.0,
vsync: this,
)..addStatusListener((status) {
if (status == AnimationStatus.completed) {
//执行结束反向执行
animationController.reverse();
}else if (status == AnimationStatus.dismissed) {
//反向执行结束正向执行
animationController.forward();
}
});
animation = CurvedAnimation(parent: animationController, curve: Curves.easeIn);
animation = Tween(begin: 0.0,end: 200.0,).animate(animationController);
animationController.forward();
}
@override
Widget build(BuildContext context) {
return AnimatedContainer(
animation : animation,
);
throw UnimplementedError();
}
@override
void dispose() {
super.dispose();
animationController.dispose();
}
}
class AnimatedContainer extends AnimatedWidget {
AnimatedContainer({Key key,Animation<double> this.animation} )
: super(key:key,listenable:animation);
final Animation<double> animation;
@override
Widget build(BuildContext context) {
return Center(
child: Container(
height:animation.value,
width: animation.value,
color: Colors.green,
),
);
throw UnimplementedError();
}
}
四、AnimatedBuilder
//改变其子控件大小
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: AnimationDemo(),
);
}
}
class AnimationDemo extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _AnimationDemo();
}
}
class _AnimationDemo extends State<AnimationDemo> with SingleTickerProviderStateMixin{
AnimationController animationController;
Animation<double> animation;
@override
void initState() {
super.initState();
animationController = AnimationController(
duration: Duration(seconds: 1),
lowerBound: 0.0,
upperBound: 1.0,
vsync: this,
)..addStatusListener((status) {
if (status == AnimationStatus.completed) {
//执行结束反向执行
animationController.reverse();
}else if (status == AnimationStatus.dismissed) {
//反向执行结束正向执行
animationController.forward();
}
});
animation = CurvedAnimation(parent: animationController, curve: Curves.easeIn);
animation = Tween(begin: 0.0,end: 200.0,).animate(animationController);
animationController.forward();
}
@override
Widget build(BuildContext context) {
return Center(
child: _AnimatedBuilder (
animation: animation,
child : FlutterLogo(),
),
);
throw UnimplementedError();
}
@override
void dispose() {
super.dispose();
animationController.dispose();
}
}
class _AnimatedBuilder extends StatelessWidget {
_AnimatedBuilder({this.child,this.animation});
final Animation<double> animation;
final Widget child;
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: animation,
builder: (context,child) {
return Container(
height: animation.value,
width: animation.value,
child: child,
);
},
child: child,);
throw UnimplementedError();
}
}
AnimatedWidget 和 AnimatedBuilder的区别:
(1)AnimatedBuilder继承自AnimatedWidget,所以AnimatedWidget能实现的功能AnimatedBuilder也能实现,但AnimatedBuilder的功能更强大。
(2)AnimatedWidget可以认为是Animation的助手类,它封装了监听Animation对象的通知并调用了setState.
五、交错动画
交错动画是由多个动画组成,多个动画可以同时执行,也可以顺序执行。这些动画由一个AnimationController控制,动画是同时执行还是顺序执行由动画对象的Interval属性决定。
//控件大小和颜色同时变化
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: AnimationDemo(),
);
}
}
class AnimationDemo extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _AnimationDemo();
}
}
class _AnimationDemo extends State<AnimationDemo> with SingleTickerProviderStateMixin{
AnimationController animationController;
Animation<double> animationSize;
Animation animationColor;
@override
void initState() {
super.initState();
animationController = AnimationController(
duration: Duration(seconds: 1),
lowerBound: 0.0,
upperBound: 1.0,
vsync: this,
) ..addListener(() { })
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
//执行结束反向执行
animationController.reverse();
}else if (status == AnimationStatus.dismissed) {
//反向执行结束正向执行
animationController.forward();
}
});
// animationColor = animationSize = CurvedAnimation(parent: animationController, curve: Curves.easeIn);
animationSize = Tween(begin: 0.0,end: 1.0,).animate(animationController);
animationColor = ColorTween(begin: Colors.yellow,end: Colors.blue).animate(animationController);
animationController.forward();
}
@override
Widget build(BuildContext context) {
return Center(
child: Container(
height:( animationSize.value+ 1) * 100,
width: ( animationSize.value + 1) * 100,
color: animationColor.value,
),
);
throw UnimplementedError();
}
@override
void dispose() {
super.dispose();
animationController.dispose();
}
}
//控件大小和颜色变化按照顺序执行
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: AnimationDemo(),
);
}
}
class AnimationDemo extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _AnimationDemo();
}
}
class _AnimationDemo extends State<AnimationDemo> with SingleTickerProviderStateMixin{
AnimationController animationController;
Animation<double> animationSize;
Animation animationColor;
@override
void initState() {
super.initState();
animationController = AnimationController(
duration: Duration(seconds: 1),
lowerBound: 0.0,
upperBound: 1.0,
vsync: this,
) ..addListener(() { })
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
//执行结束反向执行
animationController.reverse();
}else if (status == AnimationStatus.dismissed) {
//反向执行结束正向执行
animationController.forward();
}
});
// animationColor = animationSize = CurvedAnimation(parent: animationController, curve: Curves.easeIn);
animationSize = Tween(begin: 80.0,end: 100.0,).animate(CurvedAnimation(parent: animationController,curve: Interval(0.0,0.5)));
animationColor = ColorTween(begin: Colors.yellow,end: Colors.blue).animate(CurvedAnimation(parent: animationController,curve:Interval(0.5,1.0)) );
animationController.forward();
}
@override
Widget build(BuildContext context) {
return Center(
child: Container(
height:animationSize.value,
width: animationSize.value,
color: animationColor.value,
),
);
throw UnimplementedError();
}
@override
void dispose() {
super.dispose();
animationController.dispose();
}
}
六、AnimatedList
AnimatedList:列表中数据发生变化时加入过渡动画。AnimatedList得构建首先需要itemBuilder,itemBuilder是一个函数,列表的每一个索引会调用,这个函数有一个animation参数,可以设置成任何一个动画。如果初始的时候数据不为空,AnimatedList的构建需要initialItemCount,initialItemCount表示数据的数量。当有数据添加或者删除时,调用AnimatedListState的对应的方法:
AnimatedListState.insertItem
AnimatedListState.removeItem
得到AnimatedListState有两个方法:
(1)AnimatedList.of(context)方法:
AnimatedList.of(context).insertItem(index)
AnimatedList.of(context).removeItem(index, (context, animation) => {})
(2)设置key,
final GlobalKey<AnimatedListState> _kiytKey = GlobalKey<AnimatedListState>();
AnimatedList(
key: _kiytKey,
initialItemCount:_list.length,
itemBuilder: (BuildContext context, int index, Animation animation) {
return _buildItem(_list[index].toString(),animation);
},
);
调用如下:
_listKey.currentState.insertItem(_index);
//左进右出动画效果
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: AnimationDemo(),
);
}
}
class AnimationDemo extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _AnimationDemo();
}
}
class _AnimationDemo extends State<AnimationDemo> with SingleTickerProviderStateMixin{
List<int> _list = [];
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
void _addItem() {
final int _index = _list.length;
_list.insert(_index, _index);
_listKey.currentState.insertItem(_index);
}
void _removeItem() {
final int _index = _list.length - 1;
var item =_list[_index].toString();
_listKey.currentState.removeItem((_index), (context, animation) => _buildItem(item,animation));
_list.removeAt(_index);
}
Widget _buildItem(String _item,Animation _animation) {
return SlideTransition(
position: _animation.drive(CurveTween(curve: Curves.easeIn)).drive(Tween<Offset>(begin: Offset(1,1),end: Offset(0,1))),
child: Card(
child: ListTile(
title: Text(_item),
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: AnimatedList(
key: _listKey,
initialItemCount: _list.length,
itemBuilder: (BuildContext context,int index,Animation animation) {
return _buildItem(_list[index].toString(), animation);
},
),
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
FloatingActionButton(
onPressed: ()=>_addItem(),
child: Icon(Icons.add),
),
SizedBox(width: 60,),
FloatingActionButton(
onPressed: ()=>_removeItem(),
child: Icon(Icons.remove),),
],
),
);
throw UnimplementedError();
}
@override
void dispose() {
super.dispose();
}
}
七、Hero
Hero是一种常见的动画效果,是可以再路由(页面)之间“飞行”的widget,可以从一个页面打开另一个页面时产生一个简单的过滤动画。构建Hero需要一个子控件,这里设置其为图片;还需要一个tag,第一个页面喝第二个页面的Hero控件的atg属性值要保持一致。
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: HeroDemo(),
);
}
}
class HeroDemo extends StatelessWidget{
@override
Widget build(BuildContext context) {
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
itemBuilder: (context,index)=>_buildItem(context,index),
itemCount: 1,);
throw UnimplementedError();
}
_buildItem(context,index) {
return GestureDetector(
onTap: () {
print("lililililili");
Navigator.of(context).push(MaterialPageRoute(builder: (context){
return HeroDetailDemo();
}));
},
child: Hero(
tag: 'chair',
child: Container(
height: 100,
width: 100,
child: Image.asset('assets/icons/ic_launcher_test.png'),
),
),
);
}
}
class HeroDetailDemo extends StatelessWidget{
@override
Widget build(BuildContext context) {
return AspectRatio(
aspectRatio: 1,
child: Hero(
tag: 'chair',
child: Image.asset('assets/icons/ic_launcher_test.png'),
),
);
throw UnimplementedError();
}
}