Flutter-动画-实践篇
Posted 819158327fan
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 混合开发获取 BinaryMessenger 报错 ( FlutterActivityAndFragmentDelegate.getFlutterEngine() )(代码片段