吃豆人加载动画效果是Loading动画系列中的一个,github地址:https://github.com/LaoMengFlutter/flutter-do
Loading动画效果如下
其中吃豆人加载动画效果如下
下面我们看看吃豆人加载动画效果是如何实现的?动画效果实现的思路是绘制一个静止的效果,其中可变的效果使用参数控制,回到我们的吃豆人加载动画,先绘制一个中间状态,效果如下:
吃豆人分为2部分,第一部分是左侧的头,第二部分是豆子,也就是小圆点。先看第一部分左侧的头,其实就是一个圆弧,控制其张开的角度,代码如下:
代码语言:javascript复制class _PacmanPainter extends CustomPainter {
final double angle;
final Color color;
Paint _paint = Paint()..style = PaintingStyle.fill;
_PacmanPainter(
this.angle, {
this.color = Colors.white,
}) {
_paint.color = color;
}
@override
void paint(Canvas canvas, Size size) {
var _radius = min(size.width, size.height) / 2;
canvas.drawArc(Rect.fromLTWH(0, 0, _radius * 2, _radius * 2), angle / 2,
2 * pi - angle, true, _paint);
}
@override
bool shouldRepaint(covariant _PacmanPainter old) {
return color != old.color ||
angle != old.angle;
}
}
增加动画控制,使其达到张/闭嘴的效果,代码如下:
代码语言:javascript复制late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
_controller =
AnimationController(vsync: this, duration: widget.mouthDuration)
..repeat(reverse: true);
_animation = Tween(begin: 0.0, end: pi / 2)
.animate(CurvedAnimation(parent: _controller, curve: widget.curve));
super.initState();
}
AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return CustomPaint(
painter:
_PacmanPainter(_animation.value, color: widget.mouthColor),
);
},
)
然后我们在看下豆子的实现,这个更简单,就是绘制多个小圆点,同时向左移动,使用一个变量控制其左偏移,代码如下:
代码语言:javascript复制class _PointTranslatePainter extends CustomPainter {
final double progress;
final int count;
final Color color;
final double radius;
Paint _paint = Paint()..style = PaintingStyle.fill;
_PointTranslatePainter(
this.progress, {
this.count = 5,
this.color = Colors.white,
this.radius = 3.0,
}) {
_paint.color = color;
}
@override
void paint(Canvas canvas, Size size) {
var _perX = size.width / (count - 1);
for (int i = 0; i < count * 2; i ) {
var _x = _perX * i - size.width * progress;
if (_x >= 0 && _x <= size.width) {
canvas.drawCircle(Offset(_x, size.height / 2), radius, _paint);
}
}
}
@override
bool shouldRepaint(covariant _PointTranslatePainter old) {
return color != old.color ||
progress != old.progress ||
count != old.count ||
radius != old.radius;
}
}
增加向左移动的动画控制:
代码语言:javascript复制late AnimationController _controller, _controller1;
late Animation<double> _animation;
@override
void initState() {
_controller =
AnimationController(vsync: this, duration: widget.mouthDuration)
..repeat(reverse: true);
_controller1 =
AnimationController(vsync: this, duration: widget.ballDuration)
..repeat();
_animation = Tween(begin: 0.0, end: pi / 2)
.animate(CurvedAnimation(parent: _controller, curve: widget.curve));
super.initState();
}
AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return CustomPaint(
painter: _PointTranslatePainter(_controller1.value,
color: widget.ballColor),
);
},
)
然后我们将这2部分叠加到一起,就是吃豆人的效果,完整代码如下:
代码语言:javascript复制import 'dart:math';
import 'dart:ui';
import 'package:flutter/material.dart';
///
/// desc: 吃豆人
///
class PacmanLoading extends StatefulWidget {
final Color mouthColor;
final Color ballColor;
final Duration mouthDuration;
final Duration ballDuration;
final Curve curve;
const PacmanLoading(
{Key? key,
this.mouthColor = Colors.white,
this.ballColor = Colors.white,
this.mouthDuration = const Duration(milliseconds: 800),
this.ballDuration = const Duration(milliseconds: 2000),
this.curve = Curves.linear})
: super(key: key);
@override
_PacmanLoadingState createState() => _PacmanLoadingState();
}
class _PacmanLoadingState extends State<PacmanLoading>
with TickerProviderStateMixin {
late AnimationController _controller, _controller1;
late Animation<double> _animation;
@override
void initState() {
_controller =
AnimationController(vsync: this, duration: widget.mouthDuration)
..repeat(reverse: true);
_controller1 =
AnimationController(vsync: this, duration: widget.ballDuration)
..repeat();
_animation = Tween(begin: 0.0, end: pi / 2)
.animate(CurvedAnimation(parent: _controller, curve: widget.curve));
super.initState();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
Positioned.fill(
left: 3,
child: AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return CustomPaint(
painter: _PointTranslatePainter(_controller1.value,
color: widget.ballColor),
);
},
),
),
Positioned.fill(
child: AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return CustomPaint(
painter:
_PacmanPainter(_animation.value, color: widget.mouthColor),
);
},
),
),
],
);
}
}
class _PacmanPainter extends CustomPainter {
final double angle;
final Color color;
Paint _paint = Paint()..style = PaintingStyle.fill;
_PacmanPainter(
this.angle, {
this.color = Colors.white,
}) {
_paint.color = color;
}
@override
void paint(Canvas canvas, Size size) {
var _radius = min(size.width, size.height) / 2;
canvas.drawArc(Rect.fromLTWH(0, 0, _radius * 2, _radius * 2), angle / 2,
2 * pi - angle, true, _paint);
}
@override
bool shouldRepaint(covariant _PacmanPainter old) {
return color != old.color ||
angle != old.angle;
}
}
class _PointTranslatePainter extends CustomPainter {
final double progress;
final int count;
final Color color;
final double radius;
Paint _paint = Paint()..style = PaintingStyle.fill;
_PointTranslatePainter(
this.progress, {
this.count = 5,
this.color = Colors.white,
this.radius = 3.0,
}) {
_paint.color = color;
}
@override
void paint(Canvas canvas, Size size) {
var _perX = size.width / (count - 1);
for (int i = 0; i < count * 2; i ) {
var _x = _perX * i - size.width * progress;
if (_x >= 0 && _x <= size.width) {
canvas.drawCircle(Offset(_x, size.height / 2), radius, _paint);
}
}
}
@override
bool shouldRepaint(covariant _PointTranslatePainter old) {
return color != old.color ||
progress != old.progress ||
count != old.count ||
radius != old.radius;
}
}
到这里,我们就完成了,如果你有比较酷炫的加载动画效果想要实现,可以将效果发给我,我来实现,或者已经实现的动画效果想要分享给大家,也可以发给我,我会加到github中。