28、Flutter之组件过度动画,物理动画,隐式动画,显式动画,交织动画
概述
动画一般可分为两类:
[补间动画]:补间动画是一种预先定义物体运动的起点和终点,物体的运动方式,运动时间,时间曲线,然后从起点过渡到终点的动画。
「基于物理的动画」:基于物理的动画是一种模拟现实世界运动的动画,通过建立运动模型来实现。例如一个篮球 从高处落下,需要根据其下落高度,重力加速度,地面反弹力等影响因素来建立运动模型。
隐式动画
隐式动画使用 Flutter 框架内置的动画部件创建,通过设置动画的起始值和最终值来触发。当使用 setState
方法改变部件的动画属性值时,框架会自动计算出一个从旧值过渡到新值的动画。
比如 AnimatedOpacity
部件,改变它的 opacity
值就可以触发动画。
代码:
class OpacityChangePage extends StatefulWidget {
const OpacityChangePage({Key? key}) : super(key: key);
@override
State<OpacityChangePage> createState() => _OpacityChangePageState();
}
class _OpacityChangePageState extends State<OpacityChangePage> {
double _opacity = 1.0;
//改变目标值
void _toggle(){
_opacity = _opacity >0 ?0.0:1.0;
setState(() {
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: getAppBar("隐式动画"),
body: Center(
child: AnimatedOpacity(opacity: _opacity, duration: Duration(seconds: 2),
child: Container(width: 200,height: 200,color: Colors.yellow,),
)
),
floatingActionButton: FloatingActionButton(onPressed: _toggle,child: Icon(Icons.play_arrow),),
);
}
}
显式动画
显式动画指的是需要手动设置动画的时间,运动曲线,取值范围的动画。将值传递给动画部件如: RotationTransition
,最后使用一个AnimationController
控制动画的开始和结束。
代码:
import 'dart:math';
import 'package:demo202112/utils/common_appbar.dart';
import 'package:flutter/material.dart';
/// @Author wywinstonwy
/// @Date 2022/10/25 09:03
/// @Description:
class RotationAnimationPage extends StatefulWidget {
const RotationAnimationPage({Key? key}) : super(key: key);
@override
State<RotationAnimationPage> createState() => _RotationAnimationPageState();
}
class _RotationAnimationPageState extends State<RotationAnimationPage> with SingleTickerProviderStateMixin{
late AnimationController _controller;
late Animation<double> _turns;
bool _playing = false;
//控制动画状态
void _toggle(){
if(_playing){
_playing = false;
_controller.stop();
}else{
_controller.forward()..whenComplete(() => _controller.reverse());
_playing = true;
}
setState(() {
});
}
@override
void initState() {
// TODO: implement initState
super.initState();
//初始化动画控制器,设置动画时间
_controller = AnimationController(vsync: this,duration: Duration(seconds: 10));
//设置动画取值范围和时间曲线
_turns = Tween(begin: 0.0, end: pi * 2).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeIn),
);
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
_controller.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: getAppBar("显示动画"),
body: Center(
child: RotationTransition(
turns: _turns,
child: Container(
width: 200,
height: 200,
child: Image.asset("images/室内_风扇02.png",fit: BoxFit.cover,),
),
),
),
floatingActionButton: FloatingActionButton(onPressed: _toggle,
child: Icon(_playing ? Icons.pause : Icons.play_arrow),
),
);
}
}
除了 RotationTransition
外,还有其他的显示动画部件如:FadeTransition
, ScaleTransition
, SizeTransition
, SlideTransition
等。
交织动画
交织动画是由一系列的小动画组成的动画。每个小动画可以是连续或间断的,也可以相互重叠。其关键点在于使用 Interval
部件给每个小动画设置一个时间间隔,以及为每个动画的设置一个取值范围 Tween
,最后使用一个 AnimationController
控制总体的动画状态。
Interval
继承至 Curve
类,通过设置属性 begin
和 end
来确定这个小动画的运行范围。
class Interval extends Curve {
/// 动画起始点
final double begin;
/// 动画结束点
final double end;
/// 动画缓动曲线
final Curve curve;
/// ...
}
这是一个由 5 个小动画组成的交织动画,宽度,高度,颜色,圆角,边框,每个动画都有自己的动画区间。
代码:
import 'package:demo202112/utils/common_appbar.dart';
import 'package:flutter/material.dart';
/// @Author wywinstonwy
/// @Date 2022/10/25 09:53
/// @Description:
class StaggeredAnimationPage extends StatefulWidget {
const StaggeredAnimationPage({Key? key}) : super(key: key);
@override
State<StaggeredAnimationPage> createState() => _StaggeredAnimationPageState();
}
class _StaggeredAnimationPageState extends State<StaggeredAnimationPage> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _width;
late Animation<double> _height;
late Animation<Color> _color;
late Animation<double> _border;
late Animation<BorderRadius> _borderRadius;
void _play(){
if(_controller.isCompleted){
_controller.reverse();
}else{
_controller.forward();
}
}
@override
void initState() {
// TODO: implement initState
super.initState();
_controller = AnimationController(vsync: this,
duration: Duration(seconds:5)
);
//宽度变化
_width = Tween<double>(begin: 100,end: 300).animate(CurvedAnimation(
parent: _controller,
curve:Interval(0.0, 0.2,curve: Curves.ease),
));
//高度变化
_height = Tween<double>(begin: 100,end: 300).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(0.2,0.4,curve: Curves.ease)
)
);
//颜色变化
_color = Tween(begin: Colors.blue,end: Colors.yellow).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(0.4,0.6,curve: Curves.ease)
)
) ;
_borderRadius = Tween(
begin: BorderRadius.circular(0.0),
end: BorderRadius.circular(150.0),
).animate(CurvedAnimation(parent: _controller,
curve: Interval(0.6,0.8,curve: Curves.ease)
)) ;
_border = Tween<double>(
begin: 0,
end: 25,
).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(0.8, 1.0),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('交织动画')),
body: Center(
child: AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, Widget? child) {
return Container(
width: _width.value,
height: _height.value,
decoration: BoxDecoration(
color: _color.value,
borderRadius: _borderRadius.value,
border: Border.all(
width: _border.value,
color: Colors.cyanAccent
)
),
);
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: _play,
child: Icon(Icons.refresh),
),
);
}
}
执行过程报错:Cannot lerp between "MaterialColor(primary value: Color(0xff2196f3))" and "MaterialColor(primary value: Color(0xffffeb3b))".
To lerp colors, consider ColorTween instead.
物理动画
物理动画是一种模拟现实世界物体运动的动画。需要建立物体的运动模型,以一个物体下落为例,这个运动受到物体的下落高度,重力加速度,地面的反作用力等因素的影响。
代码:
import 'package:demo202112/utils/common_appbar.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
/// @Author wywinstonwy
/// @Date 2022/10/25 11:16
/// @Description:
class ThrowAnimationPage extends StatefulWidget {
const ThrowAnimationPage({Key? key}) : super(key: key);
@override
State<ThrowAnimationPage> createState() => _ThrowAnimationPageState();
}
class _ThrowAnimationPageState extends State<ThrowAnimationPage> {
// 球心高度
double y = 70.0;
// Y 轴速度
double vy = -10.0;
// 重力
double gravity = 0.1;
// 地面反弹力
double bounce = -0.5;
// 球的半径
double radius = 50.0;
// 地面高度
final double height = 700;
void _fall(_) {
y += vy;
vy += gravity;
//如果球体触及地面,根据地面反弹力改变球体的 Y 轴速度
if (y + radius > height) {
y = height - radius;
vy *= bounce;
} else if (y - radius < 0) {
y = 0 + radius;
vy *= bounce;
}
setState(() {});
}
@override
void initState() {
// TODO: implement initState
super.initState();
// 使用一个 Ticker 在每次更新界面时运行球体下落方法
Ticker(_fall)..start();
}
@override
Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width;
return Scaffold(
appBar: AppBar(title: Text('物理动画')),
body: Column(
children: <Widget>[
Container(
height: height,
child: Stack(
children: <Widget>[
Positioned(
top: y - radius,
left: screenWidth / 2 - radius,
child: Container(
width: radius * 2,
height: radius * 2,
decoration: const BoxDecoration(
color: Colors.blue,
shape: BoxShape.circle,
),
),
),
],
),
),
Expanded(child: Container(color: Colors.blue)),
],
),
);
}
}
组件过度动画
Widget属性发生变化时会执行过渡动画的组件统称为”动画过渡组件“,而动画过渡组件最明显的一个特征就是它会在内部自管理AnimationController。我们知道,为了方便使用者可以自定义动画的曲线、执行时长、方向等,在前面介绍过的动画封装方法中,通常都需要使用者自己提供一个AnimationController对象来自定义这些属性值。但是,如此一来,使用者就必须得手动管理AnimationController,这又会增加使用的复杂性。因此,如果也能将AnimationController进行封装,则会大大提高动画组件的易用性。
我们要实现一个AnimatedDecoratedBox
,它可以在decoration
属性发生变化时,从旧状态变成新状态的过程可以执行一个过渡动画。根据前面所学的知识,我们实现了一个AnimatedDecoratedBox1
组件:
/// @Author wywinstonwy
/// @Date 2022/10/16 10:15 上午
/// @Description:
import "package:flutter/material.dart";
class AnimatedDecoratedBox1 extends StatefulWidget {
final BoxDecoration decoration;
final Widget child;
final Duration duration;
final Curve ?curve;
final Duration? reverseDuration;
const AnimatedDecoratedBox1({
required this.decoration,
required this.child,
required this.duration,
this.curve,
this.reverseDuration});
@override
_AnimatedDecoratedBox1State createState() => _AnimatedDecoratedBox1State();
}
class _AnimatedDecoratedBox1State extends State<AnimatedDecoratedBox1> with SingleTickerProviderStateMixin{
AnimationController get controller=>_controller;
late AnimationController _controller;
late Animation<double> _animation;
late DecorationTween _tween;
@override
void initState() {
// TODO: implement initState
super.initState();
_controller = AnimationController(
duration: widget.duration,
reverseDuration: widget.reverseDuration,
vsync: this
);
_tween = DecorationTween(begin: widget.decoration);
_updateCurve();
}
void _updateCurve(){
_animation = CurvedAnimation(parent: _controller, curve: widget.curve!);
}
@override
void didUpdateWidget(covariant AnimatedDecoratedBox1 oldWidget) {
super.didUpdateWidget(oldWidget);
if(widget.curve != oldWidget) _updateCurve();
_controller.duration = widget.duration;
_controller.reverseDuration = widget.reverseDuration;
//正在执行过度动画
if(widget.decoration !=(_tween.end??_tween.begin)){
_tween
..begin =_tween.evaluate(_animation)
..end = widget.decoration;
_controller
..value=0.0
..forward();
}
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
builder: (BuildContext context, Widget? child) {
return DecoratedBox(
decoration: _tween.animate(_animation).value);
},
animation: _animation,
child:widget.child,
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
下面我们来使用AnimatedDecoratedBox1
来实现按钮点击后背景色从蓝色过渡到红色的效果:
class MyExcessiveAnimation extends StatefulWidget {
const MyExcessiveAnimation({Key? key}) : super(key: key);
@override
_MyExcessiveAnimationState createState() => _MyExcessiveAnimationState();
}
class _MyExcessiveAnimationState extends State<MyExcessiveAnimation> {
Color _decorationColor = Colors.blue;
var duration = Duration(seconds: 1);
// Curve curve = CurvedAnimation(parent: ) as Curve;
// Tween doubleTween = Tween<double>(begin: -200.0, end: 0.0);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: getAppBar("组件过度动画"),
body: Column(children: [
ElevatedButton(onPressed: (){
setState(() {
_decorationColor = Colors.red;
});
}, child: const Text('测试')),
Container(height: 44,
width: 200,
child: AnimatedDecoratedBox1(
decoration: BoxDecoration(color: _decorationColor),
child: ElevatedButton(onPressed: (){
setState(() {
_decorationColor = Colors.red;
});
},
child: const Text('AnimatedDecoratedBox',
style: TextStyle(color: Colors.black,fontSize: 14),
)
),
duration: duration,
curve: Curves.linear,
),
)
],)
,
);
}
}
总结
本文介绍了 Flutter 中多种类型的动画,分别是
- 隐式动画
- 显式动画
- 交织动画
- 基于物理的动画
- 组件过度动画
Flutter 动画基于类型化的 Animation
对象,Widgets
通过读取动画对象的当前值和监听状态变化重新运行 build
函数,不断变化 UI 形成动画效果。
一个动画的主要因素有
Animation
动画对象AnimationController
动画控制器Tween
动画取值范围Curve
动画运动曲线
更多的动画可以参考官方文档:api.flutter.dev/flutter/ani…
转载自:https://juejin.cn/post/7158376892473540616