动画控制器
AnimationController |
---|
AnimationController 用于控制动画,它包含动画的启动forward() 、停止stop() 、反向播放 reverse() 、重置动画reset() 等方法。 |
AnimationController 会在动画的每一帧,就会生成一个新的值。 |
默认情况下,AnimationController 在给定的时间段内线性的生成从 0.0 到1.0(默认区间)的数字。属于Animation<double> 类型。 |
scala
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
String _controllerValue = "0";
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 200),// 动画执行周期
lowerBound: 0, // 动画的最小值
upperBound: 1, // 设置动画的最大值
vsync: this)
..addListener(() { // Animation添加帧监听器 改变状态后调用setState()来触发UI重建
setState(() {
_controllerValue = "${_controller.value}"; // 获取每一帧动画的值
debugPrint("打印动画执行过程中控制器的值:$_controllerValue");
});
});
}
void _changeCurveValue() {
// 重置动画
_controller.reset();
//启动动画(正向执行)
_controller.forward();
}
@override
void dispose() {
super.dispose();
_controller.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Text(
_controllerValue,
style: Theme.of(context).textTheme.headlineMedium,
),
),
floatingActionButton: FloatingActionButton(
onPressed: _changeCurveValue,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
执行结果
I/flutter
I/flutter (32467): 打印动画执行过程中控制器的值:0.0
I/flutter (32467): 打印动画执行过程中控制器的值:1.3320699999999999
I/flutter (32467): 打印动画执行过程中控制器的值:2.1646099999999997
I/flutter (32467): 打印动画执行过程中控制器的值:2.6766499999999995
I/flutter (32467): 打印动画执行过程中控制器的值:3.1763999999999997
I/flutter (32467): 打印动画执行过程中控制器的值:3.67885
I/flutter (32467): 打印动画执行过程中控制器的值:4.0
停止动画
scss
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
String _controllerValue = "0";
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 1400), // 动画执行周期 14000
lowerBound: 1, // 动画的最小值
upperBound: 10, // 设置动画的最大值
vsync: this)
..addListener(() {
// Animation添加帧监听器 改变状态后调用setState()来触发UI重建
setState(() {
_controllerValue = "${_controller.value}"; // 获取每一帧动画的值
debugPrint("打印动画执行过程中控制器的值:$_controllerValue");
});
});
}
// 开始动画
void _startAnimation() {
// 重置动画
_controller.reset();
//启动动画(正向执行)
_controller.forward();
}
// 停止动画
void _stopAnimation() {
//启动动画(正向执行)
_controller.stop();
}
@override
void dispose() {
super.dispose();
_controller.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
GestureDetector(
onTap: () {
_startAnimation();
},
child: const Text('开始动画\n'),
),
GestureDetector(
onTap: () {
_stopAnimation();
},
child: const Text('停止动画\n'),
),
],
),
),
);
}
}
监听动画执行状态 (动画通知) addStatusListener
动画执行状态 |
---|
动态从开始到结束中间未间断 AnimationStatus.completed |
动态执行过程被停止 AnimationStatus.dismissed |
动画开始执行 AnimationStatus.forward |
scss
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 1400), // 动画执行周期 14000
lowerBound: 1, // 动画的最小值
upperBound: 10, // 设置动画的最大值
vsync: this)
..addListener(() {
// Animation添加帧监听器 改变状态后调用setState()来触发UI重建
setState(() {
_controllerValue = "${_controller.value}"; // 获取每一帧动画的值
debugPrint("打印动画执行过程中控制器的值:$_controllerValue");
});
})
..addStatusListener((status) {
if (status == AnimationStatus.forward) {
debugPrint("打印动画执行过程中控制器的值 开始动画");
} else if (status == AnimationStatus.completed) {
debugPrint("打印动画执行过程中控制器的值 动画完成");
} else if (status == AnimationStatus.dismissed) {
debugPrint("打印动画执行过程中控制器的值 动画结束");
}
debugPrint("打印动画执行过程中控制器的 动画状态 $status ");
});
}
按下手机物理键停止动画
scss
// 停止动画
// 退出应用
void exitApp(){
_controller.stop();
_controller.dispose();
// 用户尝试返回时执行的操作
// 返回true允许返回,返回false阻止返回
Navigator.of(context).pop();
SystemNavigator.pop();
}
@override
Widget build(BuildContext context) {
return PopScope(
canPop: false,
onPopInvoked: (didPop){
debugPrint("打印动画执行过程中 点击手机物理返回键 $didPop");
if(!didPop) {
exitApp();
}
},
child: Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
......
),
));
}
bash
I/flutter (19731): 打印动画执行过程中 点击手机物理返回键 false
I/flutter (19731): 打印动画执行过程中 点击手机物理返回键 true
I/flutter (19731): 打印动画执行过程中 执行 dispose函数
Curve
动画过渡的速率 |
---|
动画过程可以是匀速的、匀加速的或者先加速后减速等。 |
Flutter中通过Curve (曲线)来描述动画过程,我们把匀速动画称为线性的(Curves.linear),而非匀速动画称为非线性的。 |
CurvedAnimation 和AnimationController 都是Animation<double> 类型 |
CurvedAnimation 可以通过包装AnimationController 和Curve 生成一个新的动画对象 ,我们正是通过这种方式来将动画和动画执行的曲线关联起来的。 |
Curves 类是一个预置的枚举类,定义了许多常用的曲线,下面列几种常用的: Curves.linear、Curves.decelerate、Curves.ease、Curves.easeIn、Curves.easeOut、Curves.easeInOut
。
scss
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, this.title}) : super(key: key);
final String? title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 900),
vsync: this,
)..addListener(() => setState(() {}));
_animationTween(Curves.easeInOut);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _animationTween(Cubic cubic){
_animation = Tween(begin: 0.0, end: 1.0)
.animate(CurvedAnimation(parent: _controller, curve: cubic));
}
void _startAnimation() {
_controller.reset(); // 重置动画
_animationTween(Curves.easeInToLinear);
_controller.forward(); //开始动画
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
color: Colors.blue.withOpacity(0.2),
child: CustomPaint(
size: Size(MediaQuery.of(context).size.width,
MediaQuery.of(context).size.width),
painter: CharLinePainter(_animation),
),
),
GestureDetector(
onTap: () {
_startAnimation();
},
child: const Text('\n开始动画'),
)
],
),
);
}
}
ini
import 'dart:ui';
import 'package:flutter/material.dart';
class CharLinePainter extends CustomPainter {
final Animation _animation;
CharLinePainter(this._animation);
static const double basePadding = 24; //基础边界
double? startX, endX; //相对于原点x轴方向最小和最大偏移量(相对于原点的偏移量)
double? startY, endY; //相对于原点y轴方向最大和最小偏移量(相对于原点的偏移量)
double? _fixedWidth; //x轴方向:最大偏移量-最小偏移量(相对于原点的偏移量)
double? _fixedHeight; //y轴方向:最大偏移量-最小偏移量(相对于原点的偏移量)
final Path _path = Path();
final List<Offset> _pointList = []; // 保存要绘制的点的集合
@override
void paint(Canvas canvas, Size size) {
_pointList.clear();
_initBorder(size);
_drawXy(canvas);
_drawXYRulerText(canvas);
_drawLine(canvas);
_drawPoint(canvas);
}
/// 初始化边界
void _initBorder(Size size) {
startX = basePadding * 2;
endX = size.width - basePadding * 2;
startY = size.height - basePadding * 2;
endY = basePadding * 2;
_fixedWidth = endX! - startX!;
_fixedHeight = (startY! - endY!);
}
///绘制xy轴
///绘制x轴-y轴偏移量不变(y轴坐标点不变)
///绘制y轴-x轴偏移量不变(x轴坐标点不变)
void _drawXy(Canvas canvas) {
var paint = Paint()
..isAntiAlias = true
..strokeWidth = 1.0
..strokeCap = StrokeCap.square
..color = Colors.white
..style = PaintingStyle.stroke;
canvas.drawLine(
Offset(startX!, startY!), Offset(endX!, startY!), paint); //x轴
canvas.drawLine(
Offset(startX!, startY!), Offset(startX!, endY!), paint); //y轴
}
///绘制xy轴刻度+文本
void _drawXYRulerText(Canvas canvas) {
///x、y轴方向每个刻度的间距
double xRulerW = _fixedWidth! / 4; //x方向两个点之间的距离(刻度长)
double yRulerH = _fixedHeight! / 12; //y轴方向亮点之间的距离(刻度高)
for (int i = 1; i <= 4; i++) {
_initCurvePath(i - 1, xRulerW, yRulerH, canvas);
}
}
///计算曲线path
void _initCurvePath(int i, double xRulerW, double yRulerH, Canvas canvas) {
if (i == 0) {
var key = startX!;
var value = startY;
_path.moveTo(key, value!);
} else {
double preX = startX! + xRulerW * i;
double preY = (startY! - (i % 2 != 0 ? yRulerH : yRulerH * 6));
double currentX = startX! + xRulerW * (i + 1);
double currentY = (startY! - (i % 2 == 0 ? yRulerH : yRulerH * 6));
_path.cubicTo((preX + currentX) / 2, preY, (preX + currentX) / 2,
currentY, currentX, currentY);
// 保存要绘制的点点坐标
_pointList.add(Offset(currentX, currentY));
}
}
///绘制直线或曲线
void _drawLine(Canvas canvas) {
var paint = Paint()
..isAntiAlias = true
..strokeWidth = 2.0
..strokeCap = StrokeCap.round
..color = Colors.red
..style = PaintingStyle.stroke;
var pathMetrics = _path.computeMetrics(forceClosed: false);
var list = pathMetrics.toList();
var length = (list.length).toInt();
Path linePath = Path();
for (int i = 0; i < length; i++) {
// 通过动画绘制曲线
var extractPath = list[i].extractPath(
0, (list[i].length * _animation.value),
startWithMoveTo: true);
linePath.addPath(extractPath, const Offset(0, 0));
}
canvas.drawPath(linePath, paint);
}
// 绘制点
void _drawPoint(Canvas canvas) {
// 绘制点
var paintPoint = Paint()
..color = Colors.greenAccent
..strokeCap = StrokeCap.square
..strokeWidth = 5; // 设置点的大小
canvas.drawPoints(
PointMode.points, _pointList, paintPoint..color = Colors.green);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
运行效果
线性插值lerp函数
动画的原理其实就是每一帧绘制不同的内容,一般都是指定起始和结束状态,然后在一段时间内从起始状态逐渐变为结束状态,而具体某一帧的状态值会根据动画的进度来算出。
lerp 的计算一般遵循: 返回值 = a + (b - a) * t
lerp 是线性 插值,意思是返回值和动画进度t是成一次函数(y = kx + b)关系,因为一次函数的图像是一条直线,所以叫线性插值。
scala
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..repeat(reverse: true);
_animation = Tween<double>(begin: 0, end: 1).animate(_controller);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: AnimatedBuilder(
animation: _animation,
builder: (context, child) {
final color = Color.lerp(Colors.red, Colors.blue, _animation.value);
return Container(
width: 100 + 200 * _animation.value,
height: 100 + 200 * _animation.value,
color: color,
);
},
),
),
);
}
}
实现案例