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 说明 这个页面实现了 ...
随机推荐
- [Java复习] 多线程 Multithreading
Q1多线程基础 进程和线程? 进程: 1. 一段程序执行过程,动态的,相对而言程序是静态的.(与类和对象的关系类似) 2. CPU资源分配最小单元,包括CPU调度和资源管理. 3. 一个进程可以有多个 ...
- SpringMVC整合Springfox-Swagger
https://www.jianshu.com/p/ab10860945c3 验证通过 关于Swagger的简介就不占篇幅了... 本文使用的Springfox-Swagger版本为2.8.0 要整合 ...
- RabbitMQ学习之:(九)Headers Exchange (转贴+我的评论)
From: http://lostechies.com/derekgreer/2012/05/29/rabbitmq-for-windows-headers-exchanges/ RabbitMQ f ...
- RxJS 6有哪些新变化?
我们的前端工程由Angular4升级到Angular6,rxjs也要升级到rxjs6. rxjs6的语法做了很大的改动,幸亏引入了rxjs-compact包,否则升级工作会无法按时完成. 按照官方的 ...
- 安装php的sphinx扩展模块
转自 http://blog.csdn.net/fenglailea/article/details/38115821 首先你必须已经安装过了sphinx 如何安装sphinx请看:http://bl ...
- Python 常用模块(1) -- collections模块,time模块,random模块,os模块,sys模块
主要内容: 一. 模块的简单认识 二. collections模块 三. time时间模块 四. random模块 五. os模块 六. sys模块 一. 模块的简单认识 模块: 模块就是把装有特定功 ...
- v-for与v-if的优先级
原文地址 永远不要把 v-if 和 v-for 同时用在同一个元素上. 一般我们在两种常见的情况下会倾向于这样做: 为了过滤一个列表中的项目 (比如 v-for="user in users ...
- vue新增属性是否会响应式更新?
原文地址 在开发过程中,我们时常会遇到这样一种情况:当vue的data里边声明或者已经赋值过的对象或者数组(数组里边的值是对象)时,向对象中添加新的属性,如果更新此属性的值,是不会更新视图的. 根据官 ...
- 架构模式: Saga
架构模式: Saga 上下文 您已应用每服务数据库模式.每个服务都有自己的数据库.但是,某些业务事务跨越多个服务,因此您需要一种机制来确保服务之间的数据一致性.例如,假设您正在建立一个客户有信用额度的 ...
- kubeadm安装集群系列-5.其他操作
常用的一些操作记录 imagePullSecrets kubectl -n [namespace] create secret docker-registry regsecret --docker-s ...