一个App中如果能有优秀的动画效果,能让App看起来显得更加高大上。此篇我们就来介绍一下Flutter中Animation体系。
我们先来一个简单的例子,来实现透明度渐变动画:
class FadeInDemo extends StatefulWidget @override
State
_FadeInDemoState
class _FadeInDemoState extends State
double opacity
@override
Widget buildBuildContext context
Column
children: Widget
MaterialButton
child: Text
,
style: TextStylecolor: Colors.blueAccent,
,
onPressed: setState
opacity
,
,
AnimatedOpacity
duration: const Durationseconds: ,
opacity: opacity,
child: Text
,
这里我们借助于AnimatedOpacity来实现渐变:我们通过点击按钮来触发动画效果,如下图所示。
我们来总结一下实现步骤:
实现AnimatedOpacity来包裹需要实现透明度渐变动画的Widget,并指定duration和opacity参数。这俩参数也好理解:duration自然是动画时间,opacity表示透明度(取值范围为0~1,0表示透明)
触发动画:通过setState()方法,我们可以直接指定opacity的最终值(为1,即完全显示)。因为所谓的动画,肯定是有起始状态和结束状态,然后在指定的动画时间内慢慢发生变化。
2.使用AnimatedContainer来实现其他属性变化的动画.
class AnimatedContainerDemo extends StatefulWidget @override
State
_AnimatedContainerDemoState
class _AnimatedContainerDemoState extends StateAnimatedContainerDemo
Color color
double borderRadius
double margin
double
Random.nextDouble *
double
Random.nextDouble *
Color
Color0xFFFFFFFF Random.nextInt0xFFFFFFFF
void
setState
initState
@override
void
color randomColor
borderRadius randomBorderRadius
margin randomMargin
@override
Widget buildBuildContext context
Scaffold
body: Center
child: Column
children: Widget
SizedBox
width: ,
height: ,
child: AnimatedContainer
curve: Curves.easeInOutBack,
duration: const Durationmilliseconds: ,
margin: EdgeInsets.allmargin,
decoration: BoxDecoration
color: color,
borderRadius: BorderRadius.circularborderRadius,
,
,
,
MaterialButton
color: Theme.ofcontext.primaryColor,
child: Text
,
style: TextStylecolor: Colors.white,
,
onPressed: change,
,
,
,
运行效果如下:
我们这次同时修改了margin、color以及borderRadius三个属性。AnimatedContainer的使用思路和AnimatedOpacity类似:
包裹子widget,指定duration及相关属性参数
在setState方法中指定属性的动画终止状态
实际上我们刚刚介绍的两种实现方式被称之为隐式动画(implicit animation),可以理解成对于Animation子系统进行了一层封装,方便我们开发者使用。下面我们正式来介绍Animation子系统的重要组成类:
3.Animation类:通过这个类,我们可以知道当前的动画值(animation.value)以及当前的状态(通过设置对应的监听listener),但是对于屏幕上如何显示、widget如何渲染,它是不知道的,换句话说也不是它所关心的,这点从软件设计上耦合性也更低。其次,从代码角度上,它是一个抽象类:
4.AnimationController类。
从类本身上看,它是继承自Animation类的,并且泛型类型为double。从作用上来看,我们可以通过AnimationController来指定动画时长,以及它提供的forward()、reverse()方法来触发动画的执行。
5.CurvedAnimation类:同样继承自Animation类,并且泛型类型为double。它主要用来描述非线性变化的动画,有点类似Android中的属性动画的插值器。
6.Tween类.
从类的层次结构上,它有所不同,不再是继承自Animation,而是继承自Animatable。它主要用来指定起始状态和终止状态的。
好,我们已经对这四个类有了一定的了解,下面我们就从实例来看看他们是如何结合在一起使用的。
7.实例一:
class LogoDemo extends StatefulWidget @override
State createState _LogoState
class _LogoState extends State with SingleTickerProviderStateMixin
// 注释1:这里已经出现了我们前面提到的Animation和AnimationController类
Animationdouble animation
AnimationController controller
@override
void
super.initState
// 注释2:
// 在构造一个AnimationController对象时,我们需要传递两个参数
// vsync:主要为了防止一些超出屏幕之外的动画而导致的不必要的资源消耗。我们这里就传递
// this,除此之外,我们还需要使用with关键字来接入SingleTickerProviderStateMixin类型
// duration:指定动画时长
controller
AnimationControllervsync: this, duration: const Durationseconds:
// 注释3: 通过Tween对象来指定起始值和终止值,并且通过animate方法返回一个Animation对象,
// 并且设置了监听,最后在监听回调中调用setState,从而刷新页面
animation Tweendoublebegin: , end: .animatecontroller
addListener
setState
// 注释4: 启动动画
controller.forward
@override
Widget buildBuildContext context
Center
child: Container
margin: EdgeInsets.symmetricvertical: ,
// 注释5:通过Animation对象类获取对应的变化值
height: animation.value,
width: animation.value,
child: FlutterLogo,
,
@override
void
// 注释6:对controller解注册
controller.dispose
super.dispose
运行效果如图所示:
这里涉及到了两个Dart语言本身的知识点:mixin和..。mixin这里推荐一篇medium上的文章:https://medium.com/flutter-community/dart-what-are-mixins-3a72344011f3;而..很简单,它就是为了方便链式调用。我们可以看下前面addListener()方法,方法本身返回的void类型,但是我们最终却返回了一个Animation类型的对象。这其中就是..起到的作用,它可以使得方法返回调用这个方法的对象本身。
8.使用AnimatedWidget来重构上面的代码:
class LogoDemo extends StatefulWidget @override
State createState _LogoState
class _LogoState extends State with SingleTickerProviderStateMixin
Animationdouble animation
AnimationController controller
@override
void
super.initState
controller
AnimationControllervsync: this, duration: const Durationseconds:
animation Tweendoublebegin: , end: .animatecontroller
addStatusListenerstatus
status AnimationStatus.completed
controller.reverse
status AnimationStatus.dismissed
controller.forward
print
controller.forward
@override
Widget buildBuildContext context
AnimatedLogoanimation: animation
@override
void
controller.dispose
super.dispose
class AnimatedLogo extends AnimatedWidget
AnimatedLogoKey key, Animationdouble animation
superkey: key, listenable: animation
@override
Widget buildBuildContext context
final animation listenable as Animation
Center
child: Container
margin: EdgeInsets.symmetricvertical: ,
height: animation.value,
width: animation.value,
child: FlutterLogo,
,
先看效果:
大部分代码是和之前的例子是一样的,不同的是:
使用AnimatedWidget,并且Animation对象作为参数传递进来
省略了在addListener的回调里调用setState方法来触发页面刷新
这样写的好处:
省去了需要调用setState的重复代码
使得程序耦合性更低。试想一下,我们的App中有多处都需要实现Logo的resize动画,这个时候我们只需要在使用处定义Animation的描述,最后都使用这里的AnimatedLogo。这样做就使得Widget和Animation的描述进行分离。
我们可以去看一下AnimatedWidget类的源码:
abstract class AnimatedWidget extends StatefulWidget /// Creates a widget that rebuilds when the given listenable changes.
///
/// The listenable argument is required.
const AnimatedWidget
Key key,
@required this.listenable,
assertlistenable null,
superkey: key
/// The Listenable to this widget is listening.
///
/// Commonly an Animation or a ChangeNotifier.
final Listenable listenable
/// Override this method to build widgets that depend on the state of the
/// listenable e.g., the current value of the animation.
@protected
Widget buildBuildContext context
/// Subclasses typically not override this method.
@override
_AnimatedState createState _AnimatedState
@override
void debugFillPropertiesDiagnosticPropertiesBuilder properties
super.debugFillPropertiesproperties
properties.addDiagnosticsPropertyListenable, listenable
class _AnimatedState extends StateAnimatedWidget
@override
void
super.initState
widget.listenable.addListener_handleChange
@override
void didUpdateWidgetAnimatedWidget oldWidget
super.didUpdateWidgetoldWidget
widget.listenable oldWidget.listenable
oldWidget.listenable.removeListener_handleChange
widget.listenable.addListener_handleChange
@override
void
widget.listenable.removeListener_handleChange
super.dispose
void
setState
// The listenable's state is our build state, and it changed already.
@override
Widget buildBuildContext context widget.buildcontext
可以看到在initState方法里,添加了动画监听,回调的执行逻辑为_handleChange()方法,而_handleChange()的实现就是调用了setState方法,这点和我们之前在第7条中例子的写法一样。也就是说,AnimatedWidget只是做了一层封装而已。
9.使用AnimatedBuilder进一步重构上面的代码:
class LogoDemo extends StatefulWidget @override
State createState _LogoState
class _LogoState extends State with SingleTickerProviderStateMixin
Animationdouble animation
AnimationController controller
@override
void
super.initState
controller
AnimationControllervsync: this, duration: const Durationseconds:
animation Tweendoublebegin: , end: .animatecontroller
controller.forward
@override
Widget buildBuildContext context
GrowTransitionchild: LogoWidget, animation: animation
@override
void
controller.dispose
super.dispose
class LogoWidget extends StatelessWidget
@override
Widget buildBuildContext context
Container
margin: EdgeInsets.symmetricvertical: ,
child: FlutterLogo,
class GrowTransition extends StatelessWidget
GrowTransitionthis.child, this.animation
final Widget child
final Animationdouble animation
@override
Widget buildBuildContext context
Center
child: AnimatedBuilder
animation: animation,
builder: context, child Container
height: animation.value,
width: animation.value,
child: child,
,
child: child,
我们可以在任意地方使用这里GrowTransition,代码进行进一步分离。
10.使用Transition。
Flutter还为我们提供一些封装好的Transition,方便我们实现动画效果,下面我们就以ScaleTransition为例,说明如何去使用这些Transition。
class ScaleTransitionDemo extends StatefulWidget @override
State createState _LogoState
class _LogoState extends State with SingleTickerProviderStateMixin
Animationdouble animation
AnimationController controller
@override
void
super.initState
controller
AnimationControllervsync: this, duration: const Durationseconds:
animation Tweendoublebegin: , end: .animatecontroller
controller.forward
@override
Widget buildBuildContext context
Center
child: ScaleTransition
scale: animation,
child: FlutterLogo,
@override
void
controller.dispose
super.dispose
ScaleTransition在使用时需要指定两个参数:
scale: 就是一个Animation对象
child: 需要实现缩放动画的widget
最后再注意一下Tween中指定的值所表示的含义,它表示的倍数。比如我们这里end填入了10,表示动画结束状态为放大10倍。我们可以通过ScaleTransition的源码来说服大家:
class ScaleTransition extends AnimatedWidget /// Creates a scale transition.
///
/// The scale argument must not be null. The alignment argument defaults
/// to Alignment.center.
const ScaleTransition
Key key,
@required Animationdouble scale,
this.alignment Alignment.center,
this.child,
assertscale null,
superkey: key, listenable: scale
/// The animation that controls the scale of the child.
///
/// If the current value of the scale animation is v, the child will be
/// painted its normal size.
Animationdouble get scale listenable
/// The alignment of the origin of the coordinate system the scale
/// takes place, relative to the size of the box.
///
/// For example, to the origin of the scale to bottom middle, you can use
/// an alignment of , .
final Alignment alignment
/// The widget below this widget the tree.
///
/// @macro flutter.widgets.child
final Widget child
@override
Widget buildBuildContext context
final double scaleValue scale.value
final Matrix4 transform Matrix4.identity
scalescaleValue, scaleValue,
Transform
transform: transform,
alignment: alignment,
child: child,
可以看到,ScaleTransition也是继承自我们前面已经介绍过的AnimatedWidget,然后重点关注build()方法里,用到了Matrix4矩阵,这里的scale.value实际上就是Animation.value,而Matrix4.identity()..scale(),它的三个参数分别表示在x轴、y轴以及z轴上缩放的倍数。
与ScaleTransition类似的还有SlideTransition、RotationTransition等等,读者可以自己去尝试一下,这里就不在一一赘述了。
还没有评论,来说两句吧...