Flutter-动画-实践篇
一、了解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();
}
}
二、了解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();
}
}
三、动画示例
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();
}
}
2、旋转动画
旋转动画就是一个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();
}
}
3、缩放动画
本文档使用了两种方式来达到缩放的效果。一种是直接使用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();
}
}
4、透明度动画(渐隐渐现)
本文档使用了两种方式来达到渐隐渐现的效果。第一种使用了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();
}
}
5、非线性曲线动画
非线性曲线动画其主要类是使用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();
}
}
6、使用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,
),
),
),
);
}
}
7、组合动画
组合动画顾名思义就是一个或多个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,
),
),
),
),
),
);
}
}
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,
),
);
},
),
);
}
}
9、列表动画
列表动画本质上也是每个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),
),
),
),
),
),
);
}
}
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 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),
],
),
),
),
),
),
),
);
}
}
11、拖拽动画
拖拽需要使用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("拖拽"),
),
),
),
),
);
}
}
12、第三方动画
①、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) { },
),
),
),
);
}
}
②、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',
),
),
],
),
),
);
}
}
四、Demo代码地址
参考文献:
Flutter-动画-实践篇的更多相关文章
- 转:Flutter动画二
1. 介绍 本文会从代码层面去介绍Flutter动画,因此不会涉及到Flutter动画的具体使用. 1.1 Animation库 Flutter的animation库只依赖两个库,Dart库以及phy ...
- 转:Flutter动画一
1. 动画介绍 动画对于App来说,非常的重要.很多App,正是因为有了动画,所以才会觉得炫酷.移动端的动画库有非常的多,例如iOS上的Pop.web端的animate.css.Android端的An ...
- Flutter 动画详解(一)
本文主要介绍了动画的原理相关概念,对其他平台的动画做了一个简要的梳理,并简要的介绍了Flutter动画的一些知识. 1. 动画介绍 动画对于App来说,非常的重要.很多App,正是因为有了动画,所以才 ...
- 《Flutter 动画系列一》25种动画组件超全总结
动画运行的原理 任何程序的动画原理都是一样的,即:视觉暂留,视觉暂留又叫视觉暂停,人眼在观察景物时,光信号传入大脑神经,需经过一段短暂的时间,光的作用结束后,视觉形象并不立即消失,这种残留的视觉称&q ...
- 《Flutter 动画系列》组合动画
老孟导读:在前面的文章中介绍了 <Flutter 动画系列>25种动画组件超全总结 http://laomengit.com/flutter/module/animated_1/ < ...
- Flutter 动画使用
旋转动画 透明度变换动画 在Android中,可以通过View.animate()对视图进行动画处理,那在Flutter中怎样才能对Widget进行处理 在Flutter中,可以通过动画库给wid ...
- Flutter 动画鼻祖之CustomPaint
老孟导读:CustomPaint可以称之为动画鼻祖,它可以实现任何酷炫的动画和效果.CustomPaint本身没有动画属性,仅仅是绘制属性,一般情况下,CustomPaint会和动画控制配合使用,达到 ...
- SVG动画实践篇-模拟音量高低效果
git 地址:https://github.com/rainnaZR/svg-animations/tree/master/src/demo/step2/volumn 说明 这个动画的效果就是多个线条 ...
- flutter 动画双指放大图片
class GridAnimation extends StatefulWidget { @override State<StatefulWidget> createState() { r ...
- SVG动画实践篇-字母切换
git: https://github.com/rainnaZR/svg-animations/tree/master/src/pages/step2/letter.change 说明 这个页面实现了 ...
随机推荐
- SdCardUtils
import android.os.Environment; import android.os.StatFs; public class SdCardUtils { public static bo ...
- ansible简单入门
1,结构框架 Ansible 使用的是无代理体系结构,这种体系结构可以通过防止节点轮询控制机器来减少网络开销.Ansible 提供的结果框架如下所示: Ansible :运行在中央计算机上: Conn ...
- Spring-Kafka —— 消费重试机制实现
消息处理问题 在从Kafka主题接收消息之后立即处理消息的消费者的实现非常简单.不幸的是,现实要复杂得多,并且由于各种原因,消息处理可能会失败.其中一些原因是永久性问题,例如数据库约束失败或消息格式无 ...
- jenkins打开空白页
1.登录jenkins报错提示hudson.security.AccessDeniedException 解决: 修改Jenkins_home/config.xml文件里面的一段内容改成如下 < ...
- Spark GraphX初探
1. Graphx概念 针对某些领域,如社交网络.语言建模等,graph-parallel系统可以高效地执行复杂的图形算法,比一般的data-parallel系统更快. Graphx是将graph-p ...
- Vue 组件中 data 为什么必须是函数
原文地址 vue组件中的data必须是函数 类比引用数据类型 Object是引用数据类型,如果不用function 返回,每个组件的data 都是内存的同一个地址,一个数据改变了其他也改变了; jav ...
- 《基于TCP交换的电路交换与分组交换融合方法》读书笔记
简介 在论文<Is IP going to take over the world (of communications)?>中作者对IP相关的一些说法(假设)提出了质疑,并得出结论:虽然 ...
- 安装部署FastDFS
安装部署FastDFS 此篇博文是在安装好虚拟机和CentOS7的前提和转自以下几篇博客得来: 1.开启CentOS的网络连接: 1.1.网址:http://blog.csdn.net/white ...
- Elasticsearch5.x 引擎健康情况
查看引擎健康情况 [root@w]# curl -XGET "http://localhost:9200/_cat/health?v" epoch timestamp cluste ...
- Archlinux开启ssh服务命令
Archlinux开启ssh服务命令: systemctl enable sshd.service 开机启动 systemctl start sshd.service 立即启动 systemctl r ...