Flutter实例 | 显式动画组件
admin
2022-04-28 05:21:23
0

原标题:Flutter实例 | 显式动画组件

在Flutter 框架中大致有3 类与动画相关的组件,分别是隐式动画(Implicit Animation)、显式动画(Explicit Animation)及其他的动画实现方法。本章将延续第7章的内容,继续深入探讨Flutter中与动画相关的组件和知识。

01

如何选择动画组件

《Flutter 组件详解与实战》第7章“过渡动画”中介绍了一部分较为简单易用的动画组件,如AnimatedContainer、AnimatedOpacity、AnimatedSwitcher等。这些组件的特点为使用门槛较低,不要求开发者掌握太多与动画相关的知识,往往只需传入一个表示动画时长的duration参数。每当这些组件的属性值发生改变时,它们就会自动完成补间动画。这背后的实现原理和烦琐的操控细节都被隐去了,因此这类动画在Flutter框架中被称为隐式动画。

然而有时候隐式动画并不能满足实际需求。例如当动画需要循环重播、随时中断或多方协调时,往往开发者会更希望能精确地控制动画的进程。这时就应该考虑使用Flutter框架提供的“显式动画”组件了。从命名习惯而言,不同于常以Animated...开头的隐式动画组件,显式动画组件通常以...Transition结尾,如FadeTransition、SlideTransition、SizeTransition等。在使用显式动画时,开发者需要自行创建并维护一个AnimationController(动画控制器),通过它来控制动画的开始、暂停、重置、跳转、倒播等操作。

当Flutter框架提供的隐式动画和显式动画组件都不足以满足实际需求时,也可以考虑使用TweenAnimationBuilder自定义隐式动画,或使用AnimatedBuilder自定义显式动画。尤其是后者,若再配合CustomPaint等支持随意绘制的画布组件,则更可完成一切动画需求,但显然这么做难免会增加代码的难度,延长开发周期,因此实战中选择合适的动画组件相对重要。图12-1总结了在选择动画组件时应考虑的一些因素。

图12-1 如何选择合适的动画组件

02

显式动画

在Flutter框架中使用显式动画就可以通过动画控制器完成循环重播、随时中断或多方协调等相对复杂的动画需求。

1

显式动画

这是一个可用于制作旋转效果的显式动画组件,旋转角度由turns参数传入,代码如下:

RotationTransition(

turns: _controller,

child: FlutterLogo,

)

这里turns参数需要接收的是Animation double 类型,用于描述一个旋转的动画,其中double部分表示转了几圈,例如0.5就表示转了半圈,即180°,而1.0则表示旋转了360°,与完全没有旋转时的0.0效果一致,因此,若需实现不停旋转的效果,这里需要传入的是0.0~1.0的循环值。

由于AnimationController的默认区间恰好也是0.0~1.0,因此可以直接将一个动画控制器作为Animation double 值传给RotationTransition组件的turns参数,完整代码如下:

//第12章/rotation_transition_demo.dart

import'package:flutter/material.dart';

voidmain{

runApp(MyApp);

}

classMyAppextendsStatelessWidget{

@override

Widget build(BuildContext context){

returnMaterialApp(

home: Scaffold(

appBar: AppBar(title: Text( "RotationTransition Demo")),

body: Center(

child: AnimationDemo,

),

),

);

}

}

classAnimationDemoextendsStatefulWidget{

@override

_AnimationDemoState createState=> _AnimationDemoState;

}

class_AnimationDemoStateextendsState< AnimationDemo>

withSingleTickerProviderStateMixin{

late AnimationController _controller;

@override

voidinitState{

_controller = AnimationController(

duration: Duration(seconds: 1),

vsync: this,

)..repeat;

super.initState;

}

@override

voiddispose{

_controller.dispose;

super.dispose;

}

@override

Widget build(BuildContext context){

returnRotationTransition(

turns: _controller,

child: FlutterLogo(size: 80),

);

}

}

上述代码首先通过SingleTickerProviderStateMixin 得到ticker,并定义了一个AnimationController且在initState初始化时将其动画时长设置为1s,最后通过调用repeat方法使其循环播放,运行时即可观察到其子组件FlutterLogo循环旋转的动画效果,如图12-2所示。

图12‑2使用RotationTransition实现旋转效果

除了repeat(循环播放)外,动画控制器还支持forward(正序播放一次)、reverse(倒序播放一次)、stop(原地停止)、reset(重置)、animateTo(动画至某个值后再停下)等方法。

2

FadeTransition

这是一个负责改变不透明度的显式动画组件,可以算作隐式动画AnimatedOpacity组件的显式动画版本,因此它可以被动画控制器操作,随时暂停等。该组件用法与12.2.1节的RotationTransition大同小异,通过opacity参数接收不透明度值,代码如下:

FadeTransition(

opacity: _controller,

child: FlutterLogo,

)

例如可提供一些按钮让用户手动控制动画的进展,效果如图12-3所示。

图12‑3使用FadeTransition手动控制不透明度动画

用于实现图12-3所示效果的完整代码如下:

//第12章/fade_transition_demo.dart

import'package:flutter/material.dart';

voidmain{

runApp(MyApp);

}

classMyAppextendsStatelessWidget{

@override

Widget build(BuildContext context){

returnMaterialApp(

home: Scaffold(

appBar: AppBar(title: Text( "FadeTransition Demo")),

body: Center(

child: AnimationDemo,

),

),

);

}

}

classAnimationDemoextendsStatefulWidget{

@override

_AnimationDemoState createState=> _AnimationDemoState;

}

class_AnimationDemoStateextendsState< AnimationDemo>

withSingleTickerProviderStateMixin{

late AnimationController _controller;

@override

voidinitState{

_controller = AnimationController(

duration: Duration(seconds: 1),

vsync: this,

);

super.initState;

}

@override

voiddispose{

_controller.dispose;

super.dispose;

}

@override

Widget build(BuildContext context){

returnRow(

mainAxisAlignment: MainAxisAlignment.spaceEvenly,

children: [

FadeTransition(

opacity: _controller,

child: FlutterLogo(size: 80),

),

ElevatedButton(

child: Text( "开始"),

onPressed: => _controller.repeat(reverse: true),

),

ElevatedButton(

child: Text( "暂停"),

onPressed: => _controller.stop,

),

],

);

}

}

值得一提的是,上例的开始按钮调用_controller.repeat方法时传入了reverse: true参数,该参数的作用是每当动画控制器播放结束(值为1.0)时,倒序播放一遍,再循环重播。换言之,当没有传入该参数时,动画控制器默认的循环播放是从0.0渐变至1.0,然后瞬间重置至0.0,再循环播放,而设置reverse后,循环播放的行为会改为从0.0渐变至1.0,再从1.0渐变至0.0,以此循环播放。

3

ScaleTransition

这是一个可以缩放子组件的显式动画组件,用法与其他显式动画组件非常相似,不同点在于它需要通过scale参数接收缩放倍数,代码如下:

ScaleTransition(

scale: _controller,

child: FlutterLogo,

)

例如当scale为2.0时,子组件会被放大为原来的2倍,而当scale为0.5时,则子组件就会被缩小为原来的一半。

4

SizeTransition

这是一个借助ClipRect 对子组件尺寸进行裁剪的显式动画组件,运行时通过sizeFactor属性值决定对子组件的裁剪行为,代码如下:

SizeTransition(

sizeFactor: _controller,

child: FlutterLogo,

)

例如当sizeFactor的值从1.0渐变至0.0时,可观察到子组件逐渐被裁剪至完全消失的效果,如图12-4所示。该组件默认行为是沿垂直方向中心裁剪,如有必要,也可通过传入axis: Axis.horizontal改为沿水平方向裁剪,或通过axisAlignment属性改变裁剪的对齐方式。

图12‑4使用SizeTransition对子组件进行裁剪动画

12.2.3节介绍的ScaleTransition的内部是通过Transform 组件对子组件变形,从而在绘制时达到缩放的效果,而这里的SizeTransition则是通过直接改变自身尺寸,并配合ClipRect对子组件裁剪,在布局时达到需要的动画效果,然而在Flutter布局中,组件的尺寸必须满足父级约束,因此实战中要注意该组件的父级约束不应为紧约束,否则无法观察到动画效果。对组件约束等概念不熟悉的读者可参考本书第6章“进阶布局”中关于“约束”的内容,对Transform 变形或ClipRect裁剪等内容不熟悉的读者可查阅本书第14章“渲染与特效”中对这2个组件的介绍。

5

SideTransition

这是一个负责平移的显式动画组件,使用时需通过position 属性传入一个Animation Offset 表示位移程度,通常借助Tween实现,代码如下:

SlideTransition(

position: Tween(

begin: Offset( 0, 0),

end: Offset( 0.5, -1.2),

).animate(_controller),

child: FlutterLogo( size: 100),

)

上述代码通过Tween的begin参数将动画起始位置定义为Offset(0,0),又通过end参数将结束位置定义为Offset(0.5, -1.2)。这些值是指相对于该组件的尺寸,即应由初始位置偏移的比例,因此动画起始的(0,0)就表示原地不动,而结束位置的(0.5, -1.2)实际表示的是向右移动0.5倍于整个子组件的宽度,并向上移动1.2倍于子组件的高度。若子组件的尺寸为100×100单位,则实际动画时长内组件会向右平移50单位,并同时向上平移120单位。

6

PositionedTransition

这个PositionedTransition是隐式动画AnimatedPositioned组件的显式动画版本,因此这2个组件都与第1章介绍的普通Positioned组件一样,可在Stack中使用,代码如下:

Stack(

children: [

PositionedTransition(

rect: RelativeRectTween(

begin: RelativeRect.fromLTRB( 0, 0, 200, 200),

end: RelativeRect.fromLTRB( 100, 100, 0, 0),

).animate(_controller),

child: FlutterLogo( size: 100),

),

],

)

这里值得一提的是,该组件的rect参数需要的类型为Animation RelativeRect ,可由动画控制器串联RelativeRectTween得到。通过RelativeRect.fromLTRB构造函数,开发者可同时指定left、top、right、bottom 这4个方向的值,用法与Positioned组件的同名参数非常类似。

7

DecoratedBoxTransition

这个组件是普通DecoratedBox 的显式动画版本,需由decoration 参数接收类型为Animation Decoration 的值,一般由DecorationTween提供,代码如下:

DecoratedBoxTransition(

decoration: DecorationTween(

begin: BoxDecoration(

color: Colors.grey,

borderRadius: BorderRadius.circular( 50.0),

),

end: BoxDecoration(

color: Colors.black,

borderRadius: BorderRadius.zero,

),

).animate(_controller),

child: FlutterLogo( size: 100),

)

运行时该组件会由灰色的圆形装饰,渐变至黑色的矩形装饰,如图12-5所示。

图12‑5使用DecoratedBoxTransition对子组件进行裁剪动画

这个组件看似没有对应的隐式动画组件,但实际上第7章介绍的AnimatedContainer组件就支持decoration属性,也会在decoration值发生变化时自动触发隐式动画效果,可完全当作该组件的隐式动画版本。对DecoratedBox组件或BoxDecoration类不熟悉的读者可参考本书第14章“渲染与特效”中的相关内容。

8

AnimatedIcon

AnimatedIcon顾名思义,是一个用于提供动画图标的组件。它的名字虽然是以Animated...开头的,但它实际上是一个显式动画组件,需要通过progress属性传入动画控制器,另外还需要由icon属性传入动画图标数据,代码如下:

AnimatedIcon(

progress: _controller,

icon: AnimatedIcons.arrow_menu,

)

图12‑6AnimatedIcon之arrow_menu的动画效果

图12-7列出了目前Flutter内置的14种动画图标,以及它们对应的名称,供读者参考。

图12‑7Flutter内置的14种动画图标及名称

03

参考书籍

《Flutter 组件详解与实战》

ISBN:978-7-302-59420-8

[加] 王浩然(Bradley Wang) 编著

定价:109元

04

精彩推荐

  • 微信小程序游戏开发│猜数字小游戏(附源码+视频)

  • Flink编程基础│Scala编程初级实践

  • Flink编程基础│FlinkCEP编程实践

  • Flink编程基础│DataStream API编程实践

  • Flink编程基础│DataSet API编程实践

  • 数 据分析实战│客户价值分析

  • 数据分析实战│价格预测挑战

  • 数据分析实战│时间序列预测

  • 数据分析实战│KaggleTitanic生存预测

相关内容