隐式动画——Animated系列
1.1 快速开始——AnimatedContainer
//原始main.dart
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Container( //一个普普通通的Container
width: 300,
height: 300,
color: Colors.blue,
child: const Center(
child: Text("Hi",
style: TextStyle(
fontSize: 72,
)),
),
),
));
}
}
//使用AnimatedContainer,并传入Duration,两行代码就可以动起来!
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: AnimatedContainer( //将Container换成AnimatedContainer
duration: const Duration(seconds: 1), //同时加上duration参数即可
width: 300,
height: 100,
color: Colors.blue,
child: const Center(
child: Text("Hi",style: TextStyle(fontSize: 72)),
),
),
),
),
);
}
}
这里,AnimatedContainer
不能管理其child属性的变化,只能在其自身属性变化时产生动画。
值得一提的是,decoration
属性是非常好用的,例如:
AnimatedContainer(
duration: const Duration(seconds: 1),
width: 300,
height: 300,
//color: Colors.blue, //设置decoration属性后,color属性必须删除
decoration: BoxDecoration(
gradient: const LinearGradient( //设置渐变色
begin: Alignment.bottomCenter, //橙色从底部中心开始
end: Alignment.topCenter, //白色从顶部中心开始
colors: [Colors.orange, Colors.white],
stops: [0.5,0.7], //只在这个区间内设置动画效果
),
boxShadow: const [BoxShadow(spreadRadius: 5, blurRadius: 25)], //边框半径5,阴影半径25
borderRadius: BorderRadius.circular(150) //设置300/2=150,出现正圆形;设置0,变为方形
),
child: const Center(
child: Text("Hi",style: TextStyle(fontSize: 72)),
),
),
最终效果图如上。
1.2 在不同控件间切换——AnimatedSwitcher
AnimatedSwitcher主要监控child属性,如果其child属性发生变化,则会在替换控件时添加过渡动画。如果child变为null,那么该控件将渐渐消失。
AnimatedSwitcher根据 1.child的属性 2.key属性 来判断控件是否变化。
AnimatedSwitcher(
transitionBuilder: (child, animation) { //自定义动画
//return FadeTransition(opacity: animation, child: child); //默认效果:透明度渐变
//return ScaleTransition(scale: animation, child: child); //缩放动画
return RotationTransition(turns: animation, child: child); //旋转动画
},
duration: const Duration(seconds: 1), //动画持续1秒
//child: CircularProgressIndicator(),
child: Text("h22i",
key: UniqueKey(), //每次都生成一个新的key,因此AnimatedSwitcher每次都会播放切换动画
style: const TextStyle(fontSize: 100)
),
)
在transitionBuilder
中,也可以嵌套使用多种动画:
transitionBuilder: (child, animation) { //自定义动画
return FadeTransition( //不透明度渐变 + 旋转动画 的嵌套
opacity: animation,
child: RotationTransition(turns: animation, child: child), //旋转动画
);
},
如果你只有两个控件,要实现来回切换的效果,可以使用优化过后的AnimatedCrossFade
控件。
1.3 曲线——Curves.bounceOut
AnimatedOpacity(
duration: const Duration(seconds: 1),
curve: Curves.bounceOut, //设置回弹曲线
opacity: 0,
child: Container(width: 300, height: 300, color: Colors.blue),
),
可以多个Animated控件嵌套,例如:
AnimatedPadding(
duration: const Duration(seconds: 1),
padding: const EdgeInsets.only(top: 200.0),
curve: Curves.bounceOut, //设置回弹曲线,默认Curves.linear
child: AnimatedOpacity( //AnimatedPadding中嵌套AnimatedOpacity,但动画会一同开始
duration: const Duration(seconds: 1),
opacity: 0.5,
child: Container(width: 300, height: 300, color: Colors.blue),
),
),
1.4 Animated还不够?自制补间动画——TweenAnimationBuilder
使用TweenAnimationBuilder
和Tween
来创建补间动画。
//使用TweenAnimationBuilder自制一个AnimatedOpacity
TweenAnimationBuilder(
duration: const Duration(seconds: 1),
//其中,begin的值只在开始时有效,后期如果修改了end,则会从当前状态(而不是begin)前往新的end
//从begin到end期间,builder函数会被反复调用,反复渲染新的(含value)的控件
tween: Tween<double>(begin: 0.0, end: 0.5),
//builder是复杂形式的child(如ListBuilder)
builder: (BuildContext context, double value, Widget? child)=> Opacity(
opacity: value, //value也就是tween自动生成的那个值(0.0-0.5之间)
child: child //参数中的child不会被反复渲染,而会使用原来的child,达到一个优化的效果
),
child: Container(
//这里的child(也就是直属于TweenAnimationBuilder的child)就是builder函数中的child
//builder不会反复渲染这里的child,达到一个优化的效果
width: 300,
height: 300,
color: Colors.red,
),
),
使用Transform
来定义变换:
//旋转变换
TweenAnimationBuilder(
duration: const Duration(seconds: 1),
tween: Tween<double>(begin: 1.0, end: 6.0), //忽略begin,则此时begin=end,启动时动画不播放
builder: (BuildContext context, double value, Widget? child) => Container(
width: 300,
height: 300,
color: Colors.red,
// * 任何平移旋转缩放操作都能用4*4矩阵表示
//Transform( transform: Matrix4.identity().translate(x),
// * 或者使用 Transform.scale缩放 / .rotate旋转(angle:0-2pi) / .translate平移
child: Center(child: Transform.rotate( //旋转
angle: value,
child: const Text("Hi",style: TextStyle(fontSize: 70)),
),
),
),
),
//平移变换
TweenAnimationBuilder(
duration: const Duration(seconds: 1),
//tween中可以直接放置offset
tween: Tween(begin: const Offset(0,0), end: const Offset(40,0)),
builder: (BuildContext context, Offset value, Widget? child) => Container(
width: 300,
height: 300,
color: Colors.red,
child: Center(child: Transform.translate(//平移操作
offset: value,
child: const Text("Hi",style: TextStyle(fontSize: 70),),
),
),
),
),
1.5 计数器动画
实现的效果如下:
代码:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Container(
width: 300,
height: 120,
color: Colors.blue,
child: const AnimatedCounter(4,duration: Duration(seconds: 1)), //使用滚动计数器
))));
}
}
//滚动计数器
class AnimatedCounter extends StatelessWidget {
final int number;
final Duration duration;
const AnimatedCounter(this.number, {this.duration = const Duration(seconds: 1) ,Key? key})
: super(key: key);
@override
Widget build(BuildContext context) {
return TweenAnimationBuilder(
duration: duration,
tween: Tween<double>(end: number.toDouble()), //调整end
builder: (context, double? value, child) {
final whole = value! ~/ 1; //整数部分
final decimal = value - whole;
return Stack(children: [
_MyText("$whole", position: -100 * decimal, opacity: 1 - decimal),
_MyText("${whole + 1}",
position: 100 - decimal * 100, opacity: decimal),
]);
});
}
}
class _MyText extends StatelessWidget {
final String text;
final double position;
final double opacity;
const _MyText(this.text,
{required this.position, required this.opacity, Key? key})
: super(key: key);
@override
Widget build(BuildContext context) {
return Positioned(
child: Opacity(
opacity: opacity,
child: Text(text, style: const TextStyle(fontSize: 100)),
),
top: position,
);
}
}