一、了解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 extendsAnimatedWidget {
CustomAnimatedWidget({Key key, Animationanimation})
:super(key: key, listenable: animation);
Widget build(BuildContext context) {final Animation custom =animation;return newCenter(
child:newContainer(
height: animation.value,
width: animation.value,
child:newContainer(),
),
);
}
}//使用CustomAnimatedWidget
class CustomApp extendsStatefulWidget {
_CustomAppState createState()=> new_CustomAppState();
}class _CustomAppState extends Statewith SingleTickerProviderStateMixin {
AnimationController controller;
Animationanimation;
initState() {super.initState();
controller= newAnimationController(
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 newCustomAnimatedWidget(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 extendsStatelessWidget {
GrowTransition({this.child, this.animation});finalWidget child;final Animationanimation;
Widget build(BuildContext context) {return newCenter(
child:newAnimatedBuilder(
animation: animation,
builder: (BuildContext context, Widget child) {return newContainer(
height: animation.value, width: animation.value, child: child);
},
child: child),
);
}
}//使用CustomAnimatedBuilder
class CustomApp extendsStatefulWidget {
_CustomAppState createState()=> new_CustomAppState();
}class _CustomAppState extends Statewith TickerProviderStateMixin {
Animation animation;
AnimationController controller;
initState() {super.initState();
controller= newAnimationController(
duration:const Duration(milliseconds: 1000), vsync: this);final CurvedAnimation curve =
newCurvedAnimation(parent: controller, curve: Curves.easeIn);
animation= new Tween(begin: 0.0, end: 100.0).animate(curve);
controller.forward();
}
Widget build(BuildContext context) {return newCustomAnimatedBuilder(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 extendsStatefulWidget {
@override
_TransferAnimState createState()=>_TransferAnimState();
}//ignore: slash_for_doc_comments/*** 这个实现 实际上是改变 父容器的padding/margin完成的*/
class _TransferAnimState extends Statewith SingleTickerProviderStateMixin {
AnimationController _controller;
Animationanim;
AnimationslideTransition;
@overridevoidinitState() {super.initState();
_initAnim();
}
@override
Widget build(BuildContext context) {returnScaffold(
appBar: AppBar(
centerTitle:true,
title: Text("位移动画"),
),
body: Column(
children:[
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= newEdgeInsetsTween(
begin: EdgeInsets.only(left:0, top: 0),
end: EdgeInsets.only(left:100, top: 150),
).animate(_controller);//Offset 这里解释一下,是相对于自己移动的比例倍数
slideTransition = Tween(
begin: Offset(0, 0),
end: Offset(0, 2),
).animate(_controller);
anim.addListener(() {
setState(() {});
});
anim.addStatusListener((status) {
debugPrint('fanlei => $status');switch(status) {caseAnimationStatus.dismissed:
_controller?.forward();break;caseAnimationStatus.forward:break;caseAnimationStatus.reverse:break;caseAnimationStatus.completed:
_controller?.reverse();break;
}
});
_controller?.forward();
}
@overridevoiddispose() {
_controller?.dispose();super.dispose();
}
}
View Code
2、旋转动画
旋转动画就是一个Weight以某个点或者某个坐标轴旋转。可以使用Container()的transform(接受Matrix4)属性,也可以使用RotationTransition()执行旋转动画。下面看示例:
import 'package:flutter/material.dart';class RotateAnim extendsStatefulWidget {
@override
_RotateAnimState createState()=>_RotateAnimState();
}//ignore: slash_for_doc_comments
class _RotateAnimState extends Statewith SingleTickerProviderStateMixin {
AnimationController _controller;
Animationanim;
AnimationdoubleAnim;
@overridevoidinitState() {super.initState();
_initAnim();
}
@override
Widget build(BuildContext context) {returnScaffold(
appBar: AppBar(
centerTitle:true,
title: Text("旋转动画"),
),
body: Container(
child: Column(
children:[
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= newMatrix4Tween(
begin: Matrix4.rotationZ(0),
end: Matrix4.rotationZ(2.0),
).animate(_controller);
anim.addListener(() {
setState(() {});
});
anim.addStatusListener((status) {
debugPrint('fanlei => $status');switch(status) {caseAnimationStatus.dismissed:
_controller?.forward();break;caseAnimationStatus.forward:break;caseAnimationStatus.reverse:break;caseAnimationStatus.completed:
_controller?.reverse();break;
}
});
doubleAnim= Tween(
begin:0,
end:1.0,
).animate(_controller);
_controller?.forward();
}
@overridevoiddispose() {
_controller?.dispose();super.dispose();
}
}
View Code
3、缩放动画
本文档使用了两种方式来达到缩放的效果。一种是直接使用Animation改变Container()的width和height的值。另一种则是使用ScaleTransition(),其原理也是使用了Matrix4。下面看代码示例:
import 'package:flutter/material.dart';class ScaleAnim extendsStatefulWidget {
@override
_ScaleAnimState createState()=>_ScaleAnimState();
}//ignore: slash_for_doc_comments
class _ScaleAnimState extends Statewith SingleTickerProviderStateMixin {
AnimationController _controller;
AnimationanimWidth;
AnimationanimHeight;
AnimationanimScale;
@overridevoidinitState() {super.initState();
_initAnim();
}
@override
Widget build(BuildContext context) {returnScaffold(
appBar: AppBar(
centerTitle:true,
title: Text("缩放动画"),
),
body: Container(
child: Column(
children:[
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(
begin:100,
end:300,
).animate(_controller);
animWidth.addListener(() {
setState(() {});
});
animHeight= new Tween(
begin:50,
end:150,
).animate(_controller);
animScale= new Tween(
begin:1,
end:1.5,
).animate(_controller);
animWidth.addStatusListener((status) {
debugPrint('fanlei => $status');switch(status) {caseAnimationStatus.dismissed:
_controller?.forward();break;caseAnimationStatus.forward:break;caseAnimationStatus.reverse:break;caseAnimationStatus.completed:
_controller?.reverse();break;
}
});
_controller?.forward();
}
@overridevoiddispose() {
_controller?.dispose();super.dispose();
}
}
View Code
4、透明度动画(渐隐渐现)
本文档使用了两种方式来达到渐隐渐现的效果。第一种使用了Opacity(),通过Animation改变其opacity属性。第二种则是使用FadeTransition()。下面看代码示例:
import 'package:flutter/material.dart';class FadeAnim extendsStatefulWidget {
@override
_FadeAnimState createState()=>_FadeAnimState();
}//ignore: slash_for_doc_comments
class _FadeAnimState extends Statewith SingleTickerProviderStateMixin {
AnimationController _controller;
Animationanim;
AnimationfadeTransition;
@overridevoidinitState() {super.initState();
_initAnim();
}
@override
Widget build(BuildContext context) {returnScaffold(
appBar: AppBar(
centerTitle:true,
title: Text("透明度动画"),
),
body: Container(
child: Column(
children:[
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(
begin:1,
end:0.2,
).animate(_controller);
anim.addListener(() {
setState(() {});
});
anim.addStatusListener((status) {
debugPrint('fanlei => $status');switch(status) {caseAnimationStatus.dismissed:
_controller?.forward();break;caseAnimationStatus.forward:break;caseAnimationStatus.reverse:break;caseAnimationStatus.completed:
_controller?.reverse();break;
}
});
fadeTransition= Tween(
begin:0,
end:1,
).animate(_controller);
_controller?.forward();
}
@overridevoiddispose() {
_controller?.dispose();super.dispose();
}
}
View Code
5、非线性曲线动画
非线性曲线动画其主要类是使用CurvedAnimation创建一个非线性的Animation。CurvedAnimation的curve属性接受一个Curve类。Flutter SDK API中的Curves类是Flutter已经为我们写好了的各种非线性曲线。Curves非线性gif示例。下面看代码示例:
import 'package:flutter/material.dart';//本示例融合前面四种动画
class NonlinearAnim extendsStatefulWidget {
@override
_NonlinearAnimState createState()=>_NonlinearAnimState();
}class _NonlinearAnimState extends Statewith SingleTickerProviderStateMixin {//位移
AnimationController _animController;
CurvedAnimation _translateCurved;
Animation_translateAnim;
CurvedAnimation _scaleCurved;
Animation_scaleAnim;//旋转
CurvedAnimation _rotationCurved;
Animation_rotationAnim;//透明度
CurvedAnimation _opacityCurved;
Animation_opacityAnim;
@overridevoidinitState() {
_initTransfer();
_initScale();
_initRotation();
_initOpacity();
_initListener();super.initState();
}
@overridevoiddispose() {
_animController?.dispose();super.dispose();
}
@override
Widget build(BuildContext context) {returnScaffold(
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:[
GestureDetector(
onTap: () {if (!_animController.isAnimating) {
_animController.forward();
}
},
child: Container(
color: Colors.amber,
child: Stack(
children:[
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(
begin:0,
end:-100,
).animate(_translateCurved);
_translateAnim.addListener(() {
setState(() {});
});
}void_initScale() {
_scaleCurved=CurvedAnimation(parent: _animController, curve: Curves.easeInOutBack);
_scaleAnim= Tween(
begin:40,
end:140,
).animate(_scaleCurved);
}void_initRotation() {
_rotationCurved=CurvedAnimation(parent: _animController, curve: Curves.easeInOutBack);
_rotationAnim= Tween(
begin:0,
end:1,
).animate(_rotationCurved);
}void_initOpacity() {
_opacityCurved=CurvedAnimation(parent: _animController, curve: Curves.elasticInOut);
_opacityAnim= Tween(
begin:0,
end:1,
).animate(_opacityCurved);
}double _getOpacityValue(doubleopacity) {double temp = 0;if (opacity < 0) {
temp= 0;returntemp;
}if (opacity > 1) {
temp= 1;returntemp;
}
temp=opacity;returntemp;
}void_initListener() {
_animController.addStatusListener((status) {switch(status) {caseAnimationStatus.dismissed:
_animController.forward();break;caseAnimationStatus.forward://TODO: Handle this case.
break;caseAnimationStatus.reverse://TODO: Handle this case.
break;caseAnimationStatus.completed:
_animController.reverse();break;
}
});
_animController.forward();
}
}
View Code
6、使用AnimatedWidget构建可重用动画
编写一个继承AnimatedWidget的Widget,在此Widget里我们可以编写自己想要的样式并执行相应的动画。下面是代码示例:
import 'package:flutter/material.dart';class AnimWidgetPage extendsStatefulWidget {
@override
_AnimWidgetPageState createState()=>_AnimWidgetPageState();
}class _AnimWidgetPageState extends Statewith SingleTickerProviderStateMixin {
AnimationController _controller;
CurvedAnimation _curved;
Animation_anim;
@overridevoidinitState() {
_controller= AnimationController(vsync: this,duration: Duration(milliseconds: 1500));
_curved=CurvedAnimation(parent: _controller, curve: Curves.bounceInOut);
_anim= Tween(begin: 1, end: 5).animate(_curved);
_controller.forward();super.initState();
}
@overridevoiddispose() {
_controller.dispose();super.dispose();
}
@override
Widget build(BuildContext context) {returnScaffold(
appBar: AppBar(
centerTitle:true,
title: Text("AnimatedWidget"),
),
body: Container(
child: Center(
child: CustomAnimWidget(
animation: _anim,
),
),
),
);
}
}class CustomAnimWidget extendsAnimatedWidget {
CustomAnimWidget({Key key, Animationanimation})
:super(key: key, listenable: animation);
Widget build(BuildContext context) {final Animation animation =listenable;returnCenter(
child: Container(
height:200,
width:200,
child: ScaleTransition(
scale: animation,
child: Icon(
Icons.android,
color: Colors.green,
),
),
),
);
}
}
View Code
7、组合动画
组合动画顾名思义就是一个或多个Widget被几个动画同时作用。
import 'package:flutter/material.dart';class CombinationAnimPage extendsStatefulWidget {
@override
_CombinationAnimPageState createState()=>_CombinationAnimPageState();
}class _CombinationAnimPageState extends Statewith SingleTickerProviderStateMixin {
AnimationController _controller;//曲线
CurvedAnimation _curvedAnimation;//缩放
Animation_scaleAnim;//旋转
Animation_rotationAnim;
@overridevoidinitState() {
_controller= AnimationController(vsync: this,duration: Duration(milliseconds: 2000));
_curvedAnimation=CurvedAnimation(parent: _controller, curve: Curves.bounceInOut);
_scaleAnim= Tween(
begin:1,
end:5,
).animate(_curvedAnimation);
_rotationAnim= Tween(
begin:0,
end:1,
).animate(_curvedAnimation);
_controller.addStatusListener((status) {switch(status) {caseAnimationStatus.dismissed:
_controller.forward();break;caseAnimationStatus.forward://TODO: Handle this case.
break;caseAnimationStatus.reverse://TODO: Handle this case.
break;caseAnimationStatus.completed:
_controller.reverse();break;
}
});
_controller.forward();super.initState();
}
@overridevoiddispose() {
_controller.dispose();super.dispose();
}
@override
Widget build(BuildContext context) {returnScaffold(
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 extendsStatefulWidget {
@override
_CustomAnimBuildPageState createState()=>_CustomAnimBuildPageState();
}class _CustomAnimBuildPageState extends Statewith SingleTickerProviderStateMixin {
AnimationController _controller;
Animationanimation;
AnimationcolorAnim;
@overridevoidinitState() {
_controller=AnimationController(vsync:this, duration: Duration(seconds: 1));
animation= Tween(
begin:50,
end:200,
).animate(_controller);
colorAnim=ColorTween(begin: Colors.amber, end: Colors.deepPurple)
.animate(_controller);
_controller.addStatusListener((status) {switch(status) {caseAnimationStatus.dismissed:
_controller.forward();break;caseAnimationStatus.forward://TODO: Handle this case.
break;caseAnimationStatus.reverse://TODO: Handle this case.
break;caseAnimationStatus.completed:
_controller.reverse();break;
}
});
_controller.forward();super.initState();
}
@overridevoiddispose() {
_controller.dispose();super.dispose();
}
@override
Widget build(BuildContext context) {returnScaffold(
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 extendsStatelessWidget {
Widget child;
Animationanimation;
AnimationcolorAnim;
_CustomTransition({this.child, this.animation, this.colorAnim});
@override
Widget build(BuildContext context) {returnContainer(
child: AnimatedBuilder(
animation: animation,
builder: (BuildContext context, Widget widget) {returnContainer(
color: colorAnim.value,
width: animation.value,
height: animation.value,
child: Transform.translate(
offset: Offset(animation.value-50, 0),
child: child,
),
);
},
),
);
}
}
View Code
9、列表动画
列表动画本质上也是每个Item做相应的动画。下面是官方代码示例:
import 'package:flutter/material.dart';class AnimatedListSample extendsStatefulWidget {
@override
_AnimatedListSampleState createState()=> new_AnimatedListSampleState();
}class _AnimatedListSampleState extends State{final GlobalKey _listKey =
new GlobalKey();
ListModel_list;int_selectedItem;int _nextItem; //The next item inserted when the user presses the '+' button.
@overridevoidinitState() {super.initState();
_list= new ListModel(
listKey: _listKey,
initialItems:[0, 1, 2],
removedItemBuilder: _buildRemovedItem,
);
_nextItem= 3;
}//Used to build list items that haven't been removed.
Widget _buildItem(
BuildContext context,int index, Animationanimation) {return newCardItem(
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, Animationanimation) {return newCardItem(
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 newMaterialApp(
home:newScaffold(
appBar:newAppBar(
title:const Text('AnimatedList'),
actions:[newIconButton(
icon:constIcon(Icons.add_circle),
onPressed: _insert,
tooltip:'insert a new item',
),newIconButton(
icon:constIcon(Icons.remove_circle),
onPressed: _remove,
tooltip:'remove the selected item',
),
],
),
body:newPadding(
padding:const EdgeInsets.all(16.0),
child:newAnimatedList(
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{
ListModel({
@requiredthis.listKey,
@requiredthis.removedItemBuilder,
IterableinitialItems,
}) :assert(listKey != null),assert(removedItemBuilder != null),
_items= new List.from(initialItems ?? []);final GlobalKeylistKey;finaldynamic removedItemBuilder;final List_items;
AnimatedListState get _animatedList=>listKey.currentState;void insert(intindex, E item) {
_items.insert(index, item);
_animatedList.insertItem(index);
}
E removeAt(intindex) {final E removedItem =_items.removeAt(index);if (removedItem != null) {
_animatedList.removeItem(index,
(BuildContext context, Animationanimation) {returnremovedItemBuilder(removedItem, context, animation);
});
}returnremovedItem;
}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 extendsStatelessWidget {constCardItem(
{Key key,
@requiredthis.animation,this.onTap,
@requiredthis.item,this.selected: false})
:assert(animation != null),assert(item != null && item >= 0),assert(selected != null),super(key: key);final Animationanimation;finalVoidCallback onTap;final intitem;finalbool selected;
@override
Widget build(BuildContext context) {
TextStyle textStyle=Theme.of(context).textTheme.display1;if(selected)
textStyle= textStyle.copyWith(color: Colors.lightGreenAccent[400]);return newPadding(
padding:const EdgeInsets.all(2.0),
child:newSizeTransition(
axis: Axis.vertical,
sizeFactor: animation,
child:newGestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onTap,
child:newSizedBox(
height:128.0,
child:newCard(
color: Colors.primaries[item%Colors.primaries.length],
child:newCenter(
child:new Text('Item $item', style: textStyle),
),
),
),
),
),
);
}
}
View Code
10、共享元素动画
所谓共享元素动画可以简单的理解为两个页面共用同一个元素。但是其实是两个页面的的两个元素被相同的Tag所标记,再进行页面跳转的时候被框架识别,从而执行相应的动画。Flutter中使用共享元素动画需要使用Hero这个StatefulWidget。Hero的tag属性标记两个元素。下面是代码示例:
import 'package:flutter/material.dart';import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';class HeroAnimation extendsStatelessWidget {
Widget build(BuildContext context) {//timeDilation = 5.0;//1.0 means normal animation speed.
returnScaffold(
appBar: AppBar(
title: Text('Basic Hero Animation'),
centerTitle:true,
),
body: GridView.count(
crossAxisCount:2,
children:[
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) {returnScaffold(
appBar: AppBar(
title: Text("共享元素"),
centerTitle:true,
),
body: Container(
alignment: Alignment.topLeft,
child: ItemView(itemModel,400),
),
);
}
List myData = [
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'),
];//数据类型
classItemModel {
String title;
String imgUrl;
ItemModel({this.title, this.imgUrl});
}class ItemView extendsStatelessWidget {
ItemModel model;doubleheight;
ItemView(this.model, this.height);
@override
Widget build(BuildContext context) {returnGestureDetector(
onTap: () {
Navigator.of(context).push(new MaterialPageRoute(
builder: (BuildContext context) {returngetHeroAnim2(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:[
Expanded(
child: Image.network(
model.imgUrl,
fit: BoxFit.cover,
)),
Text(model.title),
],
),
),
),
),
),
),
);
}
}
View Code
11、拖拽动画
拖拽需要使用GestureDetector()监听用户手势,其函数会返回一个DragUpdateDetails对象,这个对象可以获取当前手指位移的坐标,然后通过Offset()给Widget设置偏移量。下面是代码示例:
import 'package:flutter/material.dart';class DragAnimPage extendsStatefulWidget {
@override
_DragAnimPageState createState()=>_DragAnimPageState();
}class _DragAnimPageState extends State{double mDx = 0;double mDy = 0;
GlobalKey _globalKey= newGlobalKey();
@override
Widget build(BuildContext context) {returnScaffold(
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 Code
12、第三方动画
①、Lottie
Lottie动画是Airbnb公司出的一款跨平台的动画框架(基础篇有介绍链接)。下面是代码示例:
import 'package:flutter/material.dart';import 'package:flutter_lottie/flutter_lottie.dart';class LottieAnimPage extendsStatefulWidget {
@override
_LottieAnimPageState createState()=>_LottieAnimPageState();
}class _LottieAnimPageState extends State{
@override
Widget build(BuildContext context) {returnScaffold(
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 extendsStatefulWidget {
@override
_FlareAnimPageState createState()=>_FlareAnimPageState();
}class _FlareAnimPageState extends State{
@override
Widget build(BuildContext context) {returnScaffold(
appBar: AppBar(
centerTitle:true,
title: Text("Flare动画(官方推荐)"),
),
body: Container(
child: Column(
children:[
Expanded(
child: FlareActor("assets/anim/Filip.flr",
alignment: Alignment.center,
fit: BoxFit.contain,
animation:'idle',
),
),
],
),
),
);
}
}
View Code
四、Demo代码地址
参考文献: