Flutter基础知识-动画篇

基本实现

Flutter 中有多种类型的动画,先从一个简单的例子开始。
使用一个 AnimatedContainer 控件,然后设置动画时长 duration,最后调用 setState 方法改变需要变化的属性值,一个动画就创建了

import 'package:flutter/material.dart'; class AnimatedContainerPage extends StatefulWidget { AnimatedContainerPage({Key key}) : super(key: key); @override _AnimatedContainerPageState createState() => _AnimatedContainerPageState(); } class _AnimatedContainerPageState extends State<AnimatedContainerPage> { // 初始的属性值 double size = 100; double radius = 25; Color color = Colors.yellow; void _animate() { // 改变属性值 setState(() { this.size = this.size == 100 ? 200 : 100; this.radius = this.radius == 25 ? 100 : 25; this.color = this.color == Colors.yellow ? Colors.greenAccent : Colors.yellow; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Animated Container'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ AnimatedContainer( width: this.size, height: this.size, curve: Curves.easeIn, duration: Duration(seconds: 1), padding: EdgeInsets.all(20), decoration: BoxDecoration( color: this.color, borderRadius: BorderRadius.circular(this.radius) ), child: FlutterLogo(), ) ], ), ), floatingActionButton: FloatingActionButton( onPressed: this._animate, child: Icon(Icons.refresh), ), ); } }

基础概念

补间动画 - Tween Animation

  • 根据初始状态,系统自动补充中间状态

基于物理的动画 - Physics-based animation

  • 基于物理的动画是一种遵循物理学定律的动画形式

Animation

  • Flutter 中的动画系统基于 Animation 对象

  • 它是一个抽象类,保存了当前动画的值和状态(开始、暂停、前进、倒退),但不记录屏幕上显示的内容

  • UI 元素通过读取 Animation 对象的值和监听状态变化运行 build 函数,然后渲染到屏幕上形成动画效果

  • 一个 Animation 对象在一段时间内会持续生成介于两个值之间的值,比较常见的类型是 Animation<double>,除 double 类型之外还有 Animation<Color> 或者 Animation<Size> 等。

  • animation.dart定义了动画的四种状态,以及核心的抽象类Animation

Animation的四种状态:

  • dismissed:动画的初始状态
  • forward:从头到尾播放动画
  • reverse:从尾到头播放动画
  • completed:动画完成的状态

Animation类动画过程中值变化的回调

  • void addListener(VoidCallback listener);
  • void removeListener(VoidCallback listener);

Animation类状态的回调

  • void addStatusListener(AnimationStatusListener listener);
  • void removeStatusListener(AnimationStatusListener listener);

AnimationController

  • AnimationController派生自Animation类。带有控制方法的 Animation 对象,用来控制动画的启动,暂停,结束,设定动画运行时间等。

  • 默认情况下,AnimationController是线性的产生0.01.0之间的值,每刷新一帧就产出一个数值。

  • AnimationController在不使用的时候需要dispose,否则会造成资源的泄漏

AnimationController的功能有如下几种:

  1. 播放一个动画(forward或者reverse),或者停止一个动画;
  2. 设置动画的值;
  3. 设置动画的边界值;
  4. 创建基于物理的动画效果。

Tween

  • tween.dart定义了一系列的估值器

  • Flutter通过抽象类Animatable来实现估值器

  • 用来生成不同类型和范围的动画取值

  • 最主要的子类是Tween,一个线性的估值器

以下是在Tween基础上实现的几种不同类型的估值器

  • ReverseTween
  • ColorTween
  • SizeTween
  • RectTween
  • IntTween
  • StepTween
  • ConstantTween

Curve

  • curve.dart:插值器

  • 使用 CurvedAnimation 可以将时间曲线定义为非线性曲线

  • Curve也是一个抽象类,定义了时间与数值的一个接口

Flutter定义了一系列的插值器,封装在Curves类中,有下面13种效果

  • linear
  • decelerate
  • ease
  • easeIn
  • easeOut
  • easeInOut
  • fastOutSlowIn
  • bounceIn
  • bounceOut
  • bounceInOut
  • elasticIn
  • elasticOut
  • elasticInOut

Ticker

  • Ticker 用来添加每次屏幕刷新的回调函数 TickerCallback,每次屏幕刷新都会调用。

  • 类似于 Web 里面的 requestAnimationFrame 方法

  • 主要作用是获取每一帧刷新的通知,相当于给动画添加了一个动起来的引擎

隐式动画

  • 隐式动画使用 Flutter 框架内置的动画部件创建,通过设置动画的起始值和最终值来触发。

  • 当使用 setState 方法改变部件的动画属性值时,框架会自动计算出一个从旧值过渡到新值的动画

  • 常用的动画部件:AnimatedOpacityAnimatedContainerAnimatedPaddingAnimatedPositionedAnimatedSwitcherAnimatedAlign

import 'package:flutter/material.dart'; class AnimatedContainerPage extends StatefulWidget { AnimatedContainerPage({Key key}) : super(key: key); @override _AnimatedContainerPageState createState() => _AnimatedContainerPageState(); } class _AnimatedContainerPageState extends State<AnimatedContainerPage> { double _opacity = 1; void _toggle() { setState(() { this._opacity = this._opacity > 0 ? 0 : 1; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Animated Container'), ), body: Center( child: AnimatedOpacity( opacity: this._opacity, duration: Duration(seconds: 1), child: Container( width: 200, height: 200, color: Colors.blue, ), ), ), floatingActionButton: FloatingActionButton( onPressed: this._toggle, child: Icon(Icons.play_arrow), ), ); } }

显式动画

  • 显式动画指的是需要手动设置动画的时间,运动曲线,取值范围的动画;

  • 将值传递给动画部件;最后使用一个 AnimationController 控制动画的开始和结束。

  • 常用的显示动画部件:RotationTransitionFadeTransitionScaleTransitionSizeTransitionSlideTransition

import 'package:flutter/material.dart'; import 'dart:math'; class AnimatedContainerPage extends StatefulWidget { AnimatedContainerPage({Key key}) : super(key: key); @override _AnimatedContainerPageState createState() => _AnimatedContainerPageState(); } class _AnimatedContainerPageState extends State<AnimatedContainerPage> with SingleTickerProviderStateMixin { AnimationController _controller; Animation<double> _turns; bool _playing = false; @override void initState() { super.initState(); // 初始化动画控制器,设置动画时间 this._controller = new AnimationController(vsync: this, duration: Duration(seconds: 10)); // 设置动画取值范围和时间曲线 this._turns = Tween(begin: 0.0, end: pi * 2).animate(CurvedAnimation(parent: this._controller, curve: Curves.easeIn)); } @override void dispose() { this._controller.dispose(); super.dispose(); } void _toggle() { if(this._playing) { this._playing = false; this._controller.stop(); } else { this._controller.forward()..whenComplete(() => this._controller.reverse()); this._playing = true; } setState(() { }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Animated Container'), ), body: Center( child: RotationTransition( turns: this._turns, child: Container( width: 200, height: 200, child: Image.asset('img/fan.png', fit: BoxFit.cover), ), ), ), floatingActionButton: FloatingActionButton( onPressed: this._toggle, child: Icon(this._playing ? Icons.pause : Icons.play_arrow), ), ); } }

Hero动画

  • Hero动画指的是在页面切换时一个元素从旧页面运动到新页面的动画

  • Hero动画需要使用两个 Hero 控件实现:一个用来在旧页面中,另一个在新页面。两个 Hero 控件需要使用相同的 tag 属性,并且不能与其他tag重复

交织动画

  • 交织动画是由一系列的小动画组成的动画。

  • 每个小动画可以是连续或间断的,也可以相互重叠。

  • 其关键点在于使用 Interval 部件给每个小动画设置一个时间间隔,以及为每个动画的设置一个取值范围 Tween,最后使用一个 AnimationController 控制总体的动画状态。

  • Interval 继承至 Curve 类,通过设置属性 beginend 来确定这个小动画的运行范围。

class Interval extends Curve { // 动画起始点 final double begin; // 动画结束点 final double end; // 动画缓动曲线 final Curve curve; // ... }
import 'package:flutter/material.dart'; class AnimatedContainerPage extends StatefulWidget { AnimatedContainerPage({Key key}) : super(key: key); @override _AnimatedContainerPageState createState() => _AnimatedContainerPageState(); } class _AnimatedContainerPageState extends State<AnimatedContainerPage> with SingleTickerProviderStateMixin { AnimationController _controller; Animation<double> _width; Animation<double> _height; Animation<Color> _color; Animation<double> _border; Animation<BorderRadius> _borderRadius; @override void initState() { super.initState(); // 初始化动画控制器,设置动画时间 this._controller = new AnimationController(vsync: this, duration: Duration(seconds: 5)); // 设置动画取值范围和时间曲线 this._width = Tween<double>(begin: 100, end: 300).animate(CurvedAnimation(parent: this._controller, curve: Interval(0.0, 0.2, curve: Curves.ease))); this._height = Tween<double>(begin: 100, end: 300).animate(CurvedAnimation(parent: this._controller, curve: Interval(0.2, 0.4, curve: Curves.ease))); this._color = ColorTween(begin: Colors.blue, end: Colors.yellow).animate(CurvedAnimation(parent: this._controller, curve: Interval(0.4, 0.6, curve: Curves.ease))); this._borderRadius = BorderRadiusTween(begin: BorderRadius.circular(0), end: BorderRadius.circular(150)).animate(CurvedAnimation(parent: this._controller, curve: Curves.ease)); this._border = Tween<double>(begin: 0, end: 25).animate(CurvedAnimation(parent: this._controller, curve: Interval(0.8, 1.0))); } @override void dispose() { this._controller.dispose(); super.dispose(); } void _play() { if(this._controller.isCompleted) { this._controller.reverse(); } else { this._controller.forward(); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Animated Container'), ), body: Center( child: AnimatedBuilder( animation: this._controller, builder: (BuildContext context, Widget child) { return Container( width: this._width.value, height: this._height.value, decoration: BoxDecoration( color: this._color.value, borderRadius: this._borderRadius.value, border: Border.all( width: this._border.value, color: Colors.orange ) ), ); }, ), ), floatingActionButton: FloatingActionButton( onPressed: this._play, child: Icon(Icons.refresh), ), ); } }

物理动画

  • 物理动画是一种模拟现实世界物体运动的动画,需要建立物体的运动模型。

  • 以一个物体下落为例,这个运动受到物体的下落高度,重力加速度,地面的反作用力等因素的影响

import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; class AnimatedContainerPage extends StatefulWidget { AnimatedContainerPage({Key key}) : super(key: key); @override _AnimatedContainerPageState createState() => _AnimatedContainerPageState(); } class _AnimatedContainerPageState extends State<AnimatedContainerPage> with SingleTickerProviderStateMixin { // 球心高度 double y = 70; // Y 轴速度 double vy = -10; // 重力 double gravity = 0.1; // 地面反弹力 double bounce = -0.5; // 球的半径 double radius = 50; // 地面高度 final double height = 600; @override void initState() { super.initState(); // 使用一个 Ticker 在每次更新界面时运行球体下落方法 Ticker(this._fall)..start(); } void _fall(_) { this.y += this.vy; this.vy += this.gravity; // 如果球体接触到地面,根据地面反弹力改变球体的 Y 轴速度 if(this.y + this.radius > this.height) { this.y = this.height - this.radius; this.vy *= this.bounce; } else if(this.y - this.radius < 0) { this.y = 0 + this.radius; this.vy *= this.bounce; } setState(() { }); } @override Widget build(BuildContext context) { double screenWidth = MediaQuery.of(context).size.width; print(this.height); return Scaffold( appBar: AppBar( title: Text('Animated Container'), ), body: Column( children: <Widget>[ Container( height: this.height, child: Stack( children: <Widget>[ Positioned( top: this.y - this.radius, left: screenWidth / 2 - this.radius, child: Container( width: this.radius * 2, height: this.radius * 2, decoration: BoxDecoration( color: Colors.blue, shape: BoxShape.circle ), ), ), ], ), ), Expanded( child: Container(color: Colors.blue), ) ], ), ); } }

相关文章


创作不易,若本文对你有帮助,欢迎打赏支持作者!

 分享给好友: