0
点赞
收藏
分享

微信扫一扫

【译】带有Flutter的粒子动画

在上一篇​​《【译】Flutter中的花式背景动画》​​ 中介绍了如何使用 ​​simple_animations​​ 快速实现一个漂亮的背景动画,这次将向你展示另外一种使用 ​​simple_animations​​ 创建的漂亮粒子动画的实现。

【译】带有Flutter的粒子动画_flutter

动画由一个渐变的背景,并且有大量的气泡从底部到顶部升起,然后显示一些文本内容。

气泡

这个动画最有趣的部分就是气泡,我在这个粒子动画系统中使用了大约有 30 个气泡,创建气泡时它会在底部选择一个随机的起始位置,在顶部选择一个随机的目标位置,并且它也具有随机大小以及随机速度

如果气泡到达顶部,它将再次被随机的各种属性重新创建,看起来像这样:

【译】带有Flutter的粒子动画_Flutter_02

粒子模型

如下代码所示是我们粒子模型的 dart 代码:

class ParticleModel {
Animatable tween;
double size;
AnimationProgress animationProgress;
Random random;

ParticleModel(this.random) {
restart();
}

restart({Duration time = Duration.zero}) {
final startPosition = Offset(-0.2 + 1.4 * random.nextDouble(), 1.2);
final endPosition = Offset(-0.2 + 1.4 * random.nextDouble(), -0.2);
final duration = Duration(milliseconds: 500 + random.nextInt(1000));

tween = MultiTrackTween([
Track("x").add(
duration, Tween(begin: startPosition.dx, end: endPosition.dx),
curve: Curves.easeInOutSine),
Track("y").add(
duration, Tween(begin: startPosition.dy, end: endPosition.dy),
curve: Curves.easeIn),
]);
animationProgress = AnimationProgress(duration: duration, startTime: time);
size = 0.2 + random.nextDouble() * 0.4;
}

maintainRestart(Duration time) {
if (animationProgress.progress(time) == 1.0) {
restart(time: time);
}
}
}

如上代码可以看到,在这里我们传入了一个随机生成器,然后执行了 ​​restart​​​ 粒子的动画,在 ​​restart​​ 函数中,我们定义了粒子在屏幕中的开始位置和结束位置;

  • 对于 y 值,是 0.0 表示顶部,1.0 表示在底部,并且 1.2 是比底部低 20%。;
  • 对于 x 值,其表示相似。

然后这个过程中气泡的位置和大小是随机的,我们使用这些位置来创建补间动画的值,这里使用了 ​​simple_animations​​ 的 ​​MultiTrackTween​​ 来支持一次插入多个补间属性(x,y)。

我们希望 x 的位置和 y 的位置具有不同的动画效果,并且可以实现一些不错缓慢移动的效果。

接着我们使用 ​​simple_animations​​ 的 ​​AnimationProgress​​ 创建一个对象,用于对补间动画提供的实际进度,这里需要的是一个开始时间和一个持续时间:

  • 这里通过​​restart​​ 函数传递开始时间。
  • 对于持续时间我们选择一些随机值​​500 + random.nextInt(1000)​

最后提供一个 ​​maintainRestart​​​ 函数 用于我们外部调用,以检查是否需要重新启动粒子动画,它通过调用 ​​AnimationProgressprogress(time)​​​ 的 ​​progress​​ 函数来查询状态。这个值介于 0.0 - 1.0 之间。

绘制粒子

前面准备好了动画粒子的生命周期模型之后,现在可以开始绘制它们了,这里我们使用 Flutter 的 ​​CustomPainter​​ 来绘制粒子列表:

class ParticlePainter extends CustomPainter {
List<ParticleModel> particles;
Duration time;

ParticlePainter(this.particles, this.time);

@override
void paint(Canvas canvas, Size size) {
final paint = Paint()..color = Colors.white.withAlpha(50);

particles.forEach((particle) {
var progress = particle.animationProgress.progress(time);
final animation = particle.tween.transform(progress);
final position =
Offset(animation["x"] * size.width, animation["y"] * size.height);
canvas.drawCircle(position, size.width * 0.2 * particle.size, paint);
});
}

@override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}

在 ​​paint​​ 函数中,我们循环列表中的所有粒子并查询其进度值, 然后将这些进度值传递到指定的补间动画中,以获取动画的实际相对位置, 最后我们将它们乘以画布的大小,就可以获得得到需要绘制的绝对位置。

组成控件

到这里我们完成了粒子模型和绘制,如下代码所示,现在可以创建一个渲染它们的控件:

class Particles extends StatefulWidget {
final int numberOfParticles;

Particles(this.numberOfParticles);

@override
_ParticlesState createState() => _ParticlesState();
}

class _ParticlesState extends State<Particles> {
final Random random = Random();

final List<ParticleModel> particles = [];

@override
void initState() {
List.generate(widget.numberOfParticles, (index) {
particles.add(ParticleModel(random));
});
super.initState();
}

@override
Widget build(BuildContext context) {
return Rendering(
builder: (context, time) {
_simulateParticles(time);
return CustomPaint(
painter: ParticlePainter(particles, time),
);
},
);
}

_simulateParticles(Duration time) {
particles.forEach((particle) => particle.maintainRestart(time));
}
}

这里我们创建一个有状态的控件,该控件在初始化时创建了一些粒子模型, 然后在 ​​build​​​函数使用 ​​Rendering​​ 控件(来自​​simple_animations​​​),该控件会为我们提供的 ​​Painter​​ 和生命周期需要的时间片段。

这个时间从零开始然后实时计数,我们可以利用这段时间来创建固定帧速率的动画,这也是前面 ​​AnimationProgress​​ 基于时间的原因,结果将如下所示:

【译】带有Flutter的粒子动画_前端_03

看起来还不错,但这里有一个问题,由于所有 30 个粒子在开始时都重新开始,因此会出现屏幕上部没有气泡的情况。

时间旅行

为了解决这个问题,我们需要告诉渲染控件去得到一个不同的开始时间:

@override
Widget build(BuildContext context) {
return Rendering(
startTime: Duration(seconds: 30),
onTick: _simulateParticles,
builder: (context, time) {
return CustomPaint(
painter: ParticlePainter(particles, time),
);
},
);
}

我们可以添加一个参数 ​​startTime​​​,该参数将使“ 渲染” 控件可以快速计算出你需要的间隔开始时间,然后我们将粒子动画的起始相关的代码放入 ​​onTick​​ 函数中,然后再开始动画时,所有气泡从一开始就在屏幕上分布良好:

【译】带有Flutter的粒子动画_控件_04

最后

对于背景渐变在上一篇​​《【译】Flutter中的花式背景动画》​​ 中已经介绍过,这里最后就是将所有控件件都放到 ​​Stack​​ 上:

class ParticleBackgroundApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Stack(children: <Widget>[
Positioned.fill(child: AnimatedBackground()),
Positioned.fill(child: Particles(30)),
Positioned.fill(child: CenteredText()),
]);
}
}

这是最终结果:

【译】带有Flutter的粒子动画_ide_05

这是应用到 ​​CarGuo/gsy_github_app_flutter​​ 项目登录页的效果:

【译】带有Flutter的粒子动画_控件_06

资源推荐

  • 本文 Demo 可见 :​​gsy_flutter_demo/tree/master/lib/widget/particle/​​
  • Github :​​github.com/CarGuo​​
  • 开源 Flutter 完整项目:​​github.com/CarGuo/GSYG…​​
  • 开源 Flutter 多案例学习型项目: ​​github.com/CarGuo/GSYF…​​
  • 开源 Fluttre 实战电子书项目:​​github.com/CarGuo/GSYF…​​
  • 开源 React Native 项目:​​github.com/CarGuo/GSYG…​​

【译】带有Flutter的粒子动画_ide_07

举报

相关推荐

0 条评论