一、了解AnimatedWidget
- 通常我们给一个Widget添加动画的时候都需要监听Animation的addListener()方法,并在这个方法里面不停的调用setState()方法通知Weight进行重绘。
- AnimatedWidget是Flutter封装的用于执行动画的助手类。使用它可以使我们创建一个可重用动画的Widget。而且我们也不必关心Weight在什么时候需要重绘,因为AnimatedWidget中会自动调用addListener()和setState()。
- AnimatedWidget实际上是一个StatefulWidget,它里面是定义了一套Widget并由外部将执行的动画传进来,然后根据Animation的value使各个Widget做出相应的改变。
- Flutter封装了很多AnimatedWidget的助手类。SlideTransition、AlignTransition、PositionedTransition、RelativePositionedTransition等等。
// 自定义AnimatedWidget
class CustomAnimatedWidget extends AnimatedWidget {
CustomAnimatedWidget({Key key, Animation<double> animation})
: super(key: key, listenable: animation);
Widget build(BuildContext context) {
final Animation<double> custom = animation;
return new Center(
child: new Container(
height: animation.value,
width: animation.value,
child: new Container(),
),
);
}
}
// 使用CustomAnimatedWidget
class CustomApp extends StatefulWidget {
_CustomAppState createState() => new _CustomAppState();
}
class _CustomAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<double> animation;
initState() {
super.initState();
controller = new AnimationController(
duration: const Duration(milliseconds: 5000), vsync: this);
animation = new Tween(begin: 0.0, end: 100.0).animate(controller);
controller.forward();
}
Widget build(BuildContext context) {
return new CustomAnimatedWidget(animation: animation);
}
dispose() {
controller.dispose();
super.dispose();
}
}
知识兔View Code二、了解AnimatedBuilder
- AnimatedBuilder相比较AnimatedWidget来说更加纯粹。它不知道如何渲染Widget,也不会管理Animatiion。个人感觉AnimatedWidget是一个执行具体动画的控件,而AnimatedBuilder则是一个执行特定动画的容器。
- Flutter中也创建了很多基于AnimatedBuilder的控件。例如:BottomSheet、 PopupMenu、ProgressIndicator、RefreshIndicator、Scaffold、SnackBar、TabBar、TextField。
// 自定义CustomAnimatedBuilder
class CustomAnimatedBuilder 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),
);
}
}
// 使用CustomAnimatedBuilder
class CustomApp extends StatefulWidget {
_CustomAppState createState() => new _CustomAppState();
}
class _CustomAppState extends State<LogoApp> with TickerProviderStateMixin {
Animation animation;
AnimationController controller;
initState() {
super.initState();
controller = new AnimationController(
duration: const Duration(milliseconds: 1000), vsync: this);
final CurvedAnimation curve =
new CurvedAnimation(parent: controller, curve: Curves.easeIn);
animation = new Tween(begin: 0.0, end: 100.0).animate(curve);
controller.forward();
}
Widget build(BuildContext context) {
return new CustomAnimatedBuilder(child: ‘自己的view’, animation: animation);
}
dispose() {
controller.dispose();
super.dispose();
}
}
知识兔View Code三、动画示例
1、位移动画
只要Widget能在一定的时间内按照一定的规则位移一定的距离,那边是产生了位移动画。可以通过改变Widget本身的margin,也可以通过改变父容器的padding,也可以通过SlideTransition的Offset产生位移,也可使用Matrix4的transform产生移动(Matrix4解释和使用)。下面看示例:
// 位移动画 copy 代码可以直接使用
import 'package:flutter/material.dart';
class TransferAnim extends StatefulWidget {
@override
_TransferAnimState createState() => _TransferAnimState();
}
// ignore: slash_for_doc_comments
/**
* 这个实现 实际上是改变 父容器的padding/margin完成的
*/
class _TransferAnimState extends State<TransferAnim>
with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<EdgeInsets> anim;
Animation<Offset> slideTransition;
@override
void initState() {
super.initState();
_initAnim();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text("位移动画"),
),
body: Column(
children: <Widget>[
Expanded(
child: Container(
padding: anim.value,
child: Center(
child: Container(
width: 100,
height: 50,
margin: ,
color: Colors.amber,
child: Center(
child: Text("位移动画"),
),
),
),
),
),
Expanded(
child: Container(
child: Center(
child: SlideTransition(
position: slideTransition,
child: Container(
width: 100,
height: 50,
color: Colors.amber,
child: Center(
child: Text("位移动画"),
),
),
),
),
),
),
],
),
);
}
void _initAnim() {
_controller = AnimationController(
vsync: this,
duration: Duration(
seconds: 3,
),
);
anim = new EdgeInsetsTween(
begin: EdgeInsets.only(left: 0, top: 0),
end: EdgeInsets.only(left: 100, top: 150),
).animate(_controller);
//Offset 这里解释一下,是相对于自己移动的比例倍数
slideTransition = Tween<Offset>(
begin: Offset(0, 0),
end: Offset(0, 2),
).animate(_controller);
anim.addListener(() {
setState(() {});
});
anim.addStatusListener((status) {
debugPrint('fanlei => $status');
switch (status) {
case AnimationStatus.dismissed:
_controller?.forward();
break;
case AnimationStatus.forward:
break;
case AnimationStatus.reverse:
break;
case AnimationStatus.completed:
_controller?.reverse();
break;
}
});
_controller?.forward();
}
@override
void dispose() {
_controller?.dispose();
super.dispose();
}
}
知识兔View Code2、旋转动画
旋转动画就是一个Weight以某个点或者某个坐标轴旋转。可以使用Container()的transform(接受Matrix4)属性,也可以使用RotationTransition()执行旋转动画。下面看示例:
import 'package:flutter/material.dart';
class RotateAnim extends StatefulWidget {
@override
_RotateAnimState createState() => _RotateAnimState();
}
// ignore: slash_for_doc_comments
class _RotateAnimState extends State<RotateAnim>
with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<Matrix4> anim;
Animation<double> doubleAnim;
@override
void initState() {
super.initState();
_initAnim();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text("旋转动画"),
),
body: Container(
child: Column(
children: <Widget>[
Expanded(
child: Center(
child: Container(
transform: anim.value,
width: 200,
height: 50,
color: Colors.amber,
child: Center(
child: Text("旋转动画矩阵变换"),
),
),
),
),
Expanded(
child: Center(
child: RotationTransition(
turns: doubleAnim,
child: Container(
width: 200,
height: 50,
color: Colors.greenAccent,
child: Center(
child: Text("旋转动画Rotation"),
),
),
),
),
),
],
),
),
);
}
void _initAnim() {
_controller = AnimationController(
vsync: this,
duration: Duration(
seconds: 3,
),
);
anim = new Matrix4Tween(
begin: Matrix4.rotationZ(0),
end: Matrix4.rotationZ(2.0),
).animate(_controller);
anim.addListener(() {
setState(() {});
});
anim.addStatusListener((status) {
debugPrint('fanlei => $status');
switch (status) {
case AnimationStatus.dismissed:
_controller?.forward();
break;
case AnimationStatus.forward:
break;
case AnimationStatus.reverse:
break;
case AnimationStatus.completed:
_controller?.reverse();
break;
}
});
doubleAnim = Tween<double>(
begin: 0,
end: 1.0,
).animate(_controller);
_controller?.forward();
}
@override
void dispose() {
_controller?.dispose();
super.dispose();
}
}
知识兔View Code3、缩放动画
本文档使用了两种方式来达到缩放的效果。一种是直接使用Animation改变Container()的width和height的值。另一种则是使用ScaleTransition(),其原理也是使用了Matrix4。下面看代码示例:
import 'package:flutter/material.dart';
class ScaleAnim extends StatefulWidget {
@override
_ScaleAnimState createState() => _ScaleAnimState();
}
// ignore: slash_for_doc_comments
class _ScaleAnimState extends State<ScaleAnim>
with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<double> animWidth;
Animation<double> animHeight;
Animation<double> animScale;
@override
void initState() {
super.initState();
_initAnim();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text("缩放动画"),
),
body: Container(
child: Column(
children: <Widget>[
Expanded(
child: Center(
child: Container(
width: animWidth.value,
height: animHeight.value,
color: Colors.amber,
child: Center(
child: Text("缩放动画"),
),
),
),
),
Expanded(
child: ScaleTransition(
scale: animScale,
child: Center(
child: Container(
width: 200,
height: 50,
color: Colors.greenAccent,
child: Center(
child: Text("缩放动画"),
),
),
),
),
),
],
),
),
);
}
void _initAnim() {
_controller = AnimationController(
vsync: this,
duration: Duration(
seconds: 3,
),
);
animWidth = new Tween<double>(
begin: 100,
end: 300,
).animate(_controller);
animWidth.addListener(() {
setState(() {});
});
animHeight = new Tween<double>(
begin: 50,
end: 150,
).animate(_controller);
animScale = new Tween<double>(
begin: 1,
end: 1.5,
).animate(_controller);
animWidth.addStatusListener((status) {
debugPrint('fanlei => $status');
switch (status) {
case AnimationStatus.dismissed:
_controller?.forward();
break;
case AnimationStatus.forward:
break;
case AnimationStatus.reverse:
break;
case AnimationStatus.completed:
_controller?.reverse();
break;
}
});
_controller?.forward();
}
@override
void dispose() {
_controller?.dispose();
super.dispose();
}
}
知识兔View Code4、透明度动画(渐隐渐现)
本文档使用了两种方式来达到渐隐渐现的效果。第一种使用了Opacity(),通过Animation改变其opacity属性。第二种则是使用FadeTransition()。下面看代码示例:
import 'package:flutter/material.dart';
class FadeAnim extends StatefulWidget {
@override
_FadeAnimState createState() => _FadeAnimState();
}
// ignore: slash_for_doc_comments
class _FadeAnimState extends State<FadeAnim>
with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<double> anim;
Animation<double> fadeTransition;
@override
void initState() {
super.initState();
_initAnim();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text("透明度动画"),
),
body: Container(
child: Column(
children: <Widget>[
Expanded(
child: Center(
child: Opacity(
opacity: anim.value,
child: Container(
width: 200,
height: 50,
color: Colors.amber,
child: Center(
child: Text("透明度动画"),
),
),
),
),
),
Expanded(
child: Center(
child: FadeTransition(
opacity: fadeTransition,
child: Container(
width: 200,
height: 50,
color: Colors.greenAccent,
child: Center(
child: Text("透明度动画"),
),
),
),
),
),
],
),
),
);
}
void _initAnim() {
_controller = AnimationController(
vsync: this,
duration: Duration(
seconds: 3,
),
);
anim = new Tween<double>(
begin: 1,
end: 0.2,
).animate(_controller);
anim.addListener(() {
setState(() {});
});
anim.addStatusListener((status) {
debugPrint('fanlei => $status');
switch (status) {
case AnimationStatus.dismissed:
_controller?.forward();
break;
case AnimationStatus.forward:
break;
case AnimationStatus.reverse:
break;
case AnimationStatus.completed:
_controller?.reverse();
break;
}
});
fadeTransition = Tween<double>(
begin: 0,
end: 1,
).animate(_controller);
_controller?.forward();
}
@override
void dispose() {
_controller?.dispose();
super.dispose();
}
}
知识兔View Code5、非线性曲线动画
非线性曲线动画其主要类是使用CurvedAnimation创建一个非线性的Animation。CurvedAnimation的curve属性接受一个Curve类。Flutter SDK API中的Curves类是Flutter已经为我们写好了的各种非线性曲线。Curves非线性gif示例。下面看代码示例:
import 'package:flutter/material.dart';
// 本示例融合前面四种动画
class NonlinearAnim extends StatefulWidget {
@override
_NonlinearAnimState createState() => _NonlinearAnimState();
}
class _NonlinearAnimState extends State<NonlinearAnim>
with SingleTickerProviderStateMixin {
// 位移
AnimationController _animController;
CurvedAnimation _translateCurved;
Animation<double> _translateAnim;
CurvedAnimation _scaleCurved;
Animation<double> _scaleAnim;
// 旋转
CurvedAnimation _rotationCurved;
Animation<double> _rotationAnim;
// 透明度
CurvedAnimation _opacityCurved;
Animation<double> _opacityAnim;
@override
void initState() {
_initTransfer();
_initScale();
_initRotation();
_initOpacity();
_initListener();
super.initState();
}
@override
void dispose() {
_animController?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text("非线性曲线动画"),
),
body: Container(
child: GridView.count(
crossAxisCount: 2,
padding: EdgeInsets.only(left: 10, right: 10, top: 10),
mainAxisSpacing: 10,
crossAxisSpacing: 10,
children: <Widget>[
GestureDetector(
onTap: () {
if (!_animController.isAnimating) {
_animController.forward();
}
},
child: Container(
color: Colors.amber,
child: Stack(
children: <Widget>[
Align(
alignment: Alignment.topCenter,
child: Text("bounceOut"),
),
Align(
alignment: Alignment.bottomCenter,
child: Transform.translate(
offset: Offset(0, _translateAnim.value),
child: Container(
height: 40,
width: 40,
color: Colors.white,
),
),
)
],
),
),
),
// GestureDetector(
// onTap: (){
// _animController.forward();
// },
// child: ,
// ),
GestureDetector(
onTap: () {
_animController.forward();
},
child: Container(
color: Colors.cyan,
child: Center(
child: Container(
width: _scaleAnim.value,
height: _scaleAnim.value,
color: Colors.white,
),
),
),
),
GestureDetector(
onTap: () {
_animController.forward();
},
child: Container(
color: Colors.green,
child: Center(
child: RotationTransition(
turns: _rotationAnim,
child: Container(
height: 40,
width: 40,
color: Colors.white,
),
),
),
),
),
// AnimatedOpacity(opacity: null, duration: null)
Container(
color: Colors.indigoAccent,
child: Center(
child: Opacity(
opacity:_getOpacityValue(_opacityAnim.value),
child: Container(
height: 40,
width: 40,
color: Colors.white,
),
),
),
),
],
),
),
);
}
void _initTransfer() {
_animController =
AnimationController(vsync: this, duration: Duration(seconds: 2));
_translateCurved =
CurvedAnimation(parent: _animController, curve: Curves.bounceOut);
_translateAnim = Tween<double>(
begin: 0,
end: -100,
).animate(_translateCurved);
_translateAnim.addListener(() {
setState(() {});
});
}
void _initScale() {
_scaleCurved =
CurvedAnimation(parent: _animController, curve: Curves.easeInOutBack);
_scaleAnim = Tween<double>(
begin: 40,
end: 140,
).animate(_scaleCurved);
}
void _initRotation() {
_rotationCurved =
CurvedAnimation(parent: _animController, curve: Curves.easeInOutBack);
_rotationAnim = Tween<double>(
begin: 0,
end: 1,
).animate(_rotationCurved);
}
void _initOpacity() {
_opacityCurved =
CurvedAnimation(parent: _animController, curve: Curves.elasticInOut);
_opacityAnim = Tween<double>(
begin: 0,
end: 1,
).animate(_opacityCurved);
}
double _getOpacityValue(double opacity) {
double temp = 0;
if (opacity < 0) {
temp = 0;
return temp;
}
if (opacity > 1) {
temp = 1;
return temp;
}
temp = opacity;
return temp;
}
void _initListener() {
_animController.addStatusListener((status) {
switch (status) {
case AnimationStatus.dismissed:
_animController.forward();
break;
case AnimationStatus.forward:
// TODO: Handle this case.
break;
case AnimationStatus.reverse:
// TODO: Handle this case.
break;
case AnimationStatus.completed:
_animController.reverse();
break;
}
});
_animController.forward();
}
}
知识兔View Code6、使用AnimatedWidget构建可重用动画
编写一个继承AnimatedWidget的Widget,在此Widget里我们可以编写自己想要的样式并执行相应的动画。下面是代码示例:
import 'package:flutter/material.dart';
class AnimWidgetPage extends StatefulWidget {
@override
_AnimWidgetPageState createState() => _AnimWidgetPageState();
}
class _AnimWidgetPageState extends State<AnimWidgetPage>
with SingleTickerProviderStateMixin {
AnimationController _controller;
CurvedAnimation _curved;
Animation<double> _anim;
@override
void initState() {
_controller = AnimationController(vsync: this,duration: Duration(milliseconds: 1500));
_curved = CurvedAnimation(parent: _controller, curve: Curves.bounceInOut);
_anim = Tween<double>(begin: 1, end: 5).animate(_curved);
_controller.forward();
super.initState();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text("AnimatedWidget"),
),
body: Container(
child: Center(
child: CustomAnimWidget(
animation: _anim,
),
),
),
);
}
}
class CustomAnimWidget extends AnimatedWidget {
CustomAnimWidget({Key key, Animation<double> animation})
: super(key: key, listenable: animation);
Widget build(BuildContext context) {
final Animation<double> animation = listenable;
return Center(
child: Container(
height: 200,
width: 200,
child: ScaleTransition(
scale: animation,
child: Icon(
Icons.android,
color: Colors.green,
),
),
),
);
}
}
知识兔View Code7、组合动画
组合动画顾名思义就是一个或多个Widget被几个动画同时作用。
import 'package:flutter/material.dart';
class CombinationAnimPage extends StatefulWidget {
@override
_CombinationAnimPageState createState() => _CombinationAnimPageState();
}
class _CombinationAnimPageState extends State<CombinationAnimPage>
with SingleTickerProviderStateMixin {
AnimationController _controller;
// 曲线
CurvedAnimation _curvedAnimation;
// 缩放
Animation<double> _scaleAnim;
// 旋转
Animation<double> _rotationAnim;
@override
void initState() {
_controller = AnimationController(vsync: this,duration: Duration(milliseconds: 2000));
_curvedAnimation =
CurvedAnimation(parent: _controller, curve: Curves.bounceInOut);
_scaleAnim = Tween<double>(
begin: 1,
end: 5,
).animate(_curvedAnimation);
_rotationAnim = Tween<double>(
begin: 0,
end: 1,
).animate(_curvedAnimation);
_controller.addStatusListener((status) {
switch (status) {
case AnimationStatus.dismissed:
_controller.forward();
break;
case AnimationStatus.forward:
// TODO: Handle this case.
break;
case AnimationStatus.reverse:
// TODO: Handle this case.
break;
case AnimationStatus.completed:
_controller.reverse();
break;
}
});
_controller.forward();
super.initState();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("组合动画"),
centerTitle: true,
),
body: Container(
child: Center(
child: ScaleTransition(
scale: _scaleAnim,
child: RotationTransition(
turns: _rotationAnim,
child: Icon(
Icons.android,
color: Colors.green,
),
),
),
),
),
);
}
}
知识兔View Code
8、使用AnimatedBuilder构建动画
AnimatedBuilder将动画和视图分离。它接受一个Animation和一个Widget。下面是代码示例:
import 'package:flutter/material.dart';
class CustomAnimBuildPage extends StatefulWidget {
@override
_CustomAnimBuildPageState createState() => _CustomAnimBuildPageState();
}
class _CustomAnimBuildPageState extends State<CustomAnimBuildPage>
with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<double> animation;
Animation<Color> colorAnim;
@override
void initState() {
_controller =
AnimationController(vsync: this, duration: Duration(seconds: 1));
animation = Tween<double>(
begin: 50,
end: 200,
).animate(_controller);
colorAnim = ColorTween(begin: Colors.amber, end: Colors.deepPurple)
.animate(_controller);
_controller.addStatusListener((status) {
switch (status) {
case AnimationStatus.dismissed:
_controller.forward();
break;
case AnimationStatus.forward:
// TODO: Handle this case.
break;
case AnimationStatus.reverse:
// TODO: Handle this case.
break;
case AnimationStatus.completed:
_controller.reverse();
break;
}
});
_controller.forward();
super.initState();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("AnimatedBuilder"),
centerTitle: true,
),
body: Container(
child: Center(
child: _CustomTransition(
child: Icon(
Icons.android,
size: 50,
color: Colors.green,
),
animation: animation,
colorAnim: colorAnim,
),
),
),
);
}
}
class _CustomTransition extends StatelessWidget {
Widget child;
Animation<double> animation;
Animation<Color> colorAnim;
_CustomTransition({this.child, this.animation, this.colorAnim});
@override
Widget build(BuildContext context) {
return Container(
child: AnimatedBuilder(
animation: animation,
builder: (BuildContext context, Widget widget) {
return Container(
color: colorAnim.value,
width: animation.value,
height: animation.value,
child: Transform.translate(
offset: Offset(animation.value-50, 0),
child: child,
),
);
},
),
);
}
}
知识兔View Code9、列表动画
列表动画本质上也是每个Item做相应的动画。下面是官方代码示例:
import 'package:flutter/material.dart';
class AnimatedListSample extends StatefulWidget {
@override
_AnimatedListSampleState createState() => new _AnimatedListSampleState();
}
class _AnimatedListSampleState extends State<AnimatedListSample> {
final GlobalKey<AnimatedListState> _listKey =
new GlobalKey<AnimatedListState>();
ListModel<int> _list;
int _selectedItem;
int _nextItem; // The next item inserted when the user presses the '+' button.
@override
void initState() {
super.initState();
_list = new ListModel<int>(
listKey: _listKey,
initialItems: <int>[0, 1, 2],
removedItemBuilder: _buildRemovedItem,
);
_nextItem = 3;
}
// Used to build list items that haven't been removed.
Widget _buildItem(
BuildContext context, int index, Animation<double> animation) {
return new CardItem(
animation: animation,
item: _list[index],
selected: _selectedItem == _list[index],
onTap: () {
setState(() {
_selectedItem = _selectedItem == _list[index] ? null : _list[index];
});
},
);
}
// Used to build an item after it has been removed from the list. This method is
// needed because a removed item remains visible until its animation has
// completed (even though it's gone as far this ListModel is concerned).
// The widget will be used by the [AnimatedListState.removeItem] method's
// [AnimatedListRemovedItemBuilder] parameter.
Widget _buildRemovedItem(
int item, BuildContext context, Animation<double> animation) {
return new CardItem(
animation: animation,
item: item,
selected: false,
// No gesture detector here: we don't want removed items to be interactive.
);
}
// Insert the "next item" into the list model.
void _insert() {
final int index =
_selectedItem == null ? _list.length : _list.indexOf(_selectedItem);
_list.insert(index, _nextItem++);
}
// Remove the selected item from the list model.
void _remove() {
if (_selectedItem != null) {
_list.removeAt(_list.indexOf(_selectedItem));
setState(() {
_selectedItem = null;
});
}
}
@override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: const Text('AnimatedList'),
actions: <Widget>[
new IconButton(
icon: const Icon(Icons.add_circle),
onPressed: _insert,
tooltip: 'insert a new item',
),
new IconButton(
icon: const Icon(Icons.remove_circle),
onPressed: _remove,
tooltip: 'remove the selected item',
),
],
),
body: new Padding(
padding: const EdgeInsets.all(16.0),
child: new AnimatedList(
key: _listKey,
initialItemCount: _list.length,
itemBuilder: _buildItem,
),
),
),
);
}
}
/// Keeps a Dart List in sync with an AnimatedList.
///
/// The [insert] and [removeAt] methods apply to both the internal list and the
/// animated list that belongs to [listKey].
///
/// This class only exposes as much of the Dart List API as is needed by the
/// sample app. More list methods are easily added, however methods that mutate the
/// list must make the same changes to the animated list in terms of
/// [AnimatedListState.insertItem] and [AnimatedList.removeItem].
class ListModel<E> {
ListModel({
@required this.listKey,
@required this.removedItemBuilder,
Iterable<E> initialItems,
}) : assert(listKey != null),
assert(removedItemBuilder != null),
_items = new List<E>.from(initialItems ?? <E>[]);
final GlobalKey<AnimatedListState> listKey;
final dynamic removedItemBuilder;
final List<E> _items;
AnimatedListState get _animatedList => listKey.currentState;
void insert(int index, E item) {
_items.insert(index, item);
_animatedList.insertItem(index);
}
E removeAt(int index) {
final E removedItem = _items.removeAt(index);
if (removedItem != null) {
_animatedList.removeItem(index,
(BuildContext context, Animation<double> animation) {
return removedItemBuilder(removedItem, context, animation);
});
}
return removedItem;
}
int get length => _items.length;
E operator [](int index) => _items[index];
int indexOf(E item) => _items.indexOf(item);
}
/// Displays its integer item as 'item N' on a Card whose color is based on
/// the item's value. The text is displayed in bright green if selected is true.
/// This widget's height is based on the animation parameter, it varies
/// from 0 to 128 as the animation varies from 0.0 to 1.0.
class CardItem extends StatelessWidget {
const CardItem(
{Key key,
@required this.animation,
this.onTap,
@required this.item,
this.selected: false})
: assert(animation != null),
assert(item != null && item >= 0),
assert(selected != null),
super(key: key);
final Animation<double> animation;
final VoidCallback onTap;
final int item;
final bool selected;
@override
Widget build(BuildContext context) {
TextStyle textStyle = Theme.of(context).textTheme.display1;
if (selected)
textStyle = textStyle.copyWith(color: Colors.lightGreenAccent[400]);
return new Padding(
padding: const EdgeInsets.all(2.0),
child: new SizeTransition(
axis: Axis.vertical,
sizeFactor: animation,
child: new GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onTap,
child: new SizedBox(
height: 128.0,
child: new Card(
color: Colors.primaries[item % Colors.primaries.length],
child: new Center(
child: new Text('Item $item', style: textStyle),
),
),
),
),
),
);
}
}
知识兔View Code10、共享元素动画
所谓共享元素动画可以简单的理解为两个页面共用同一个元素。但是其实是两个页面的的两个元素被相同的Tag所标记,再进行页面跳转的时候被框架识别,从而执行相应的动画。Flutter中使用共享元素动画需要使用Hero这个StatefulWidget。Hero的tag属性标记两个元素。下面是代码示例:
import 'package:flutter/material.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
class HeroAnimation extends StatelessWidget {
Widget build(BuildContext context) {
// timeDilation = 5.0; // 1.0 means normal animation speed.
return Scaffold(
appBar: AppBar(
title: Text('Basic Hero Animation'),
centerTitle: true,
),
body: GridView.count(
crossAxisCount: 2,
children: <Widget>[
ItemView(myData[0], 150),
ItemView(myData[1], 150),
ItemView(myData[2], 150),
ItemView(myData[3], 150),
ItemView(myData[4], 150),
ItemView(myData[5], 150),
ItemView(myData[6], 150),
ItemView(myData[7], 150),
ItemView(myData[8], 150),
ItemView(myData[9], 150),
],
),
);
}
}
Widget getHeroAnim2(ItemModel itemModel) {
return Scaffold(
appBar: AppBar(
title: Text("共享元素"),
centerTitle: true,
),
body: Container(
alignment: Alignment.topLeft,
child: ItemView(itemModel, 400),
),
);
}
List<ItemModel> myData = <ItemModel>[
ItemModel(
title: '啦啦啦1111',
imgUrl:
'https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=2717595227,1512397782&fm=26&gp=0.jpg'),
ItemModel(
title: '啦啦啦2222',
imgUrl:
'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3454574876,1377139334&fm=26&gp=0.jpg'),
ItemModel(
title: '啦啦啦3333',
imgUrl:
'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=1499844476,2082399552&fm=26&gp=0.jpg'),
ItemModel(
title: '啦啦啦4444',
imgUrl:
'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=1938482571,2420691429&fm=26&gp=0.jpg'),
ItemModel(
title: '啦啦啦5555',
imgUrl:
'https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3548575507,3156953806&fm=26&gp=0.jpg'),
ItemModel(
title: '啦啦啦6666',
imgUrl:
'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3484495061,2102329231&fm=26&gp=0.jpg'),
ItemModel(
title: '啦啦啦7777',
imgUrl:
'https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3562330430,950864085&fm=26&gp=0.jpg'),
ItemModel(
title: '啦啦啦8888',
imgUrl:
'https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=2985783351,2052499916&fm=26&gp=0.jpg'),
ItemModel(
title: '啦啦啦9999',
imgUrl:
'https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=311914474,2668302507&fm=26&gp=0.jpg'),
ItemModel(
title: '啦啦啦0000',
imgUrl:
'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=2471845590,913308006&fm=26&gp=0.jpg'),
];
// 数据类型
class ItemModel {
String title;
String imgUrl;
ItemModel({this.title, this.imgUrl});
}
class ItemView extends StatelessWidget {
ItemModel model;
double height;
ItemView(this.model, this.height);
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
Navigator.of(context).push(
new MaterialPageRoute<Null>(
builder: (BuildContext context) {
return getHeroAnim2(model);
},
),
);
},
child: Container(
alignment: Alignment.center,
child: SizedBox(
width: height,
height: height,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(6)),
color: Colors.white),
child: Hero(
// 一个viewTree下面不能有相同的
tag: model.imgUrl,
child: Material(
color: Colors.transparent,
child: Column(
children: <Widget>[
Expanded(
child: Image.network(
model.imgUrl,
fit: BoxFit.cover,
)),
Text(model.title),
],
),
),
),
),
),
),
);
}
}
知识兔View Code11、拖拽动画
拖拽需要使用GestureDetector()监听用户手势,其函数会返回一个DragUpdateDetails对象,这个对象可以获取当前手指位移的坐标,然后通过Offset()给Widget设置偏移量。下面是代码示例:
import 'package:flutter/material.dart';
class DragAnimPage extends StatefulWidget {
@override
_DragAnimPageState createState() => _DragAnimPageState();
}
class _DragAnimPageState extends State<DragAnimPage> {
double mDx = 0;
double mDy = 0;
GlobalKey _globalKey = new GlobalKey();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("拖拽动画"),
centerTitle: true,
),
body: Container(
child: Transform.translate(
offset: Offset(mDx, mDy),
child: GestureDetector(
onPanUpdate: (dragUpdateDetails) {
mDx = dragUpdateDetails.globalPosition.dx;
mDy = dragUpdateDetails.globalPosition.dy;
setState(() {});
},
child: Container(
width: 100,
height: 50,
alignment: Alignment.center,
color: Colors.indigoAccent,
key: _globalKey,
child: Text("拖拽"),
),
),
),
),
);
}
}
知识兔View Code12、第三方动画
①、Lottie
Lottie动画是Airbnb公司出的一款跨平台的动画框架(基础篇有介绍链接)。下面是代码示例:
import 'package:flutter/material.dart';
import 'package:flutter_lottie/flutter_lottie.dart';
class LottieAnimPage extends StatefulWidget {
@override
_LottieAnimPageState createState() => _LottieAnimPageState();
}
class _LottieAnimPageState extends State<LottieAnimPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Lottie动画"),
centerTitle: true,
),
body: Container(
padding: EdgeInsets.all(20),
child: Center(
child: LottieView.fromFile(
filePath: "assets/anim/8075-the-frog-to-drive.json",
autoPlay: true,
loop: true,
reverse: true,
onViewCreated: (lottieController) {
},
),
),
),
);
}
}
知识兔View Code②、Flare
Flare动画框架是Flutter官方推荐的一个动画框架(详细介绍请看基础篇)。下面是代码示例:
import 'package:flare_flutter/flare_actor.dart';
import 'package:flutter/material.dart';
class FlareAnimPage extends StatefulWidget {
@override
_FlareAnimPageState createState() => _FlareAnimPageState();
}
class _FlareAnimPageState extends State<FlareAnimPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text("Flare动画(官方推荐)"),
),
body: Container(
child: Column(
children: <Widget>[
Expanded(
child: FlareActor(
"assets/anim/Filip.flr",
alignment: Alignment.center,
fit: BoxFit.contain,
animation: 'idle',
),
),
],
),
),
);
}
}
知识兔View Code四、Demo代码地址
参考文献: