一、了解AnimatedWidget

  • 通常我们给一个Widget添加动画的时候都需要监听Animation的addListener()方法,并在这个方法里面不停的调用setState()方法通知Weight进行重绘。
  • AnimatedWidget是Flutter封装的用于执行动画的助手类。使用它可以使我们创建一个可重用动画的Widget。而且我们也不必关心Weight在什么时候需要重绘,因为AnimatedWidget中会自动调用addListener()和setState()。
  • AnimatedWidget实际上是一个StatefulWidget,它里面是定义了一套Widget并由外部将执行的动画传进来,然后根据Animation的value使各个Widget做出相应的改变。
  • Flutter封装了很多AnimatedWidget的助手类。SlideTransition、AlignTransitionPositionedTransitionRelativePositionedTransition等等。
// 自定义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代码地址

点击进入github项目地址

参考文献:

Flutter-动画-实践篇的更多相关文章

  1. 转:Flutter动画二

    1. 介绍 本文会从代码层面去介绍Flutter动画,因此不会涉及到Flutter动画的具体使用. 1.1 Animation库 Flutter的animation库只依赖两个库,Dart库以及phy ...

  2. 转:Flutter动画一

    1. 动画介绍 动画对于App来说,非常的重要.很多App,正是因为有了动画,所以才会觉得炫酷.移动端的动画库有非常的多,例如iOS上的Pop.web端的animate.css.Android端的An ...

  3. Flutter 动画详解(一)

    本文主要介绍了动画的原理相关概念,对其他平台的动画做了一个简要的梳理,并简要的介绍了Flutter动画的一些知识. 1. 动画介绍 动画对于App来说,非常的重要.很多App,正是因为有了动画,所以才 ...

  4. 《Flutter 动画系列一》25种动画组件超全总结

    动画运行的原理 任何程序的动画原理都是一样的,即:视觉暂留,视觉暂留又叫视觉暂停,人眼在观察景物时,光信号传入大脑神经,需经过一段短暂的时间,光的作用结束后,视觉形象并不立即消失,这种残留的视觉称&q ...

  5. 《Flutter 动画系列》组合动画

    老孟导读:在前面的文章中介绍了 <Flutter 动画系列>25种动画组件超全总结 http://laomengit.com/flutter/module/animated_1/ < ...

  6. Flutter 动画使用

    旋转动画  透明度变换动画  在Android中,可以通过View.animate()对视图进行动画处理,那在Flutter中怎样才能对Widget进行处理 在Flutter中,可以通过动画库给wid ...

  7. Flutter 动画鼻祖之CustomPaint

    老孟导读:CustomPaint可以称之为动画鼻祖,它可以实现任何酷炫的动画和效果.CustomPaint本身没有动画属性,仅仅是绘制属性,一般情况下,CustomPaint会和动画控制配合使用,达到 ...

  8. SVG动画实践篇-模拟音量高低效果

    git 地址:https://github.com/rainnaZR/svg-animations/tree/master/src/demo/step2/volumn 说明 这个动画的效果就是多个线条 ...

  9. flutter 动画双指放大图片

    class GridAnimation extends StatefulWidget { @override State<StatefulWidget> createState() { r ...

  10. SVG动画实践篇-字母切换

    git: https://github.com/rainnaZR/svg-animations/tree/master/src/pages/step2/letter.change 说明 这个页面实现了 ...

随机推荐

  1. Ajax案例-基于HTML,以GET或POST方式,检查注册用户名是否在数据库中已存在

    08_register.jsp <%@ page language="java" pageEncoding="UTF-8"%> <!DOCTY ...

  2. VBA MD5加密算法(转)

    ) ) Private Function LShift(lValue, iShiftBits) Then LShift = lValue Exit Function Then Then LShift ...

  3. Aria2Gee 教程

    改定履历 Aria2Gee是什么 开始之前 aria2 frp Aria2Gee可能存在的问题 初级教程 插件的安装 运行状态说明 下载测试 进阶教程 下载百度网盘文件 网盘助手的安装 网盘助手的配置 ...

  4. Spring Security登录超时,angular ajax请求出错自动跳转至登录页(jQuery也适用)

    公司开发采用Spring Security+AngualerJS框架,在session过期之后,ajax请求会直接出错.本文介绍如何实现出错情况下自动跳转至登录页. 整体思路是,session过期后, ...

  5. svn--备忘

  6. es6 实现单链表

    第一种/** * 链表节点类 */ class Node { constructor(ele) { this.ele = ele; this.next = null; } } /** * 链表类 */ ...

  7. java+ueditor word粘贴上传

    最近公司做项目需要实现一个功能,在网页富文本编辑器中实现粘贴Word图文的功能. 我们在网站中使用的Web编辑器比较多,都是根据用户需求来选择的.目前还没有固定哪一个编辑器 有时候用的是UEditor ...

  8. maven项目pom.xml中使用不同源的jar/自定义仓库地址

    笔者本地使用aliyun的maven仓库,在github上找了一个jar,他需求使用第三方仓库. 比如要使用https://jitpack.io上面com.github.navinilavarasan ...

  9. HTML之表单类控件、图像类元素的CSS特别样式汇总

    前言 记录下开发过程中一些特殊表单控件(input.textarea.select等)的样式控制 input 取消光标聚焦时,输入框的外延边框 input:focus{ outline:none } ...

  10. 【Java】生成随机的手机号码并输出到文件

    import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.R ...