Flutter 吃豆人加载动画效果

2021-09-03 15:59:42 浏览数 (1)

吃豆人加载动画效果是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中。

0 人点赞