如何创建像TrueCaller一样的 Flutter 动画?

oo7oh9g9  于 2023-05-30  发布在  Flutter
关注(0)|答案(1)|浏览(116)

我正在尝试开发一种类似于涟漪的效果,但它非常平滑和令人愉快。我已经尝试开发它使用动画和自定义油漆,但它不变成相同的。有没有人能达到这样的效果?你能帮我找到任何可能的方法吗?
谢谢你!

这是我使用mixin动画的代码:

class CircleAnimateWidget extends StatefulWidget {
  final double sizeMin;
  final double sizeMax;
  final double containerSize;
  final int duration;
  final int reverseDuration;

  const CircleAnimateWidget({super.key, required this.sizeMin, required this.duration, required this.sizeMax, required this.containerSize, required this.reverseDuration});

  @override
  State<StatefulWidget> createState() {
    return _State();
  }
}

class _State extends State<CircleAnimateWidget>
    with TickerProviderStateMixin {
  late Animation _sizeAnimation;
  late AnimationController _animationController;

  @override
  void initState() {
    super.initState();

    _animationController = AnimationController(
        vsync: this,
        duration: Duration(milliseconds: widget.duration),
        reverseDuration: Duration(milliseconds: widget.reverseDuration));

    _sizeAnimation = Tween(begin: widget.sizeMin, end: widget.sizeMax).animate(CurvedAnimation(
        curve: Curves.elasticOut,
        reverseCurve: Curves.ease,
        parent: _animationController));

    _animationController.addStatusListener(
          (AnimationStatus status) async {
        if (status == AnimationStatus.completed) {
          await Future.delayed(const Duration(milliseconds: 50));
          _animationController.reverse();
        } else if (status == AnimationStatus.dismissed) {
          _animationController.forward();
        }
      },
    );

    _animationController.forward();
  }

  @override
  void dispose() {
    _animationController?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: AnimatedBuilder(
        animation: _animationController,
        builder: (context, child) {
          return Container(
            width: widget.containerSize,
            height: widget.containerSize,
            alignment: Alignment.center,
            child: SizedBox(
              width: _sizeAnimation.value,
              height: _sizeAnimation.value,
              child: CircleAvatar(
                backgroundColor: Colors.green.withOpacity(0.3),
                child: const SizedBox(),
              ),
            ),
          );
        },
      ),
    );
  }
}

并将其放入堆栈:

[Positioned(
          child: CircleAnimateWidget(
            sizeMin: maxCircleSize - 20,
            sizeMax: maxCircleSize,
            duration: 1500,
            reverseDuration: 500,
            containerSize: maxCircleSize + 50,
          )
      ),
      Positioned(
          child: CircleAnimateWidget(
            sizeMin: maxCircleSize - 50,
            sizeMax: maxCircleSize - 30,
            duration: 1500,
            reverseDuration: 500,
            containerSize: maxCircleSize + 50,
          )
      ),
      Positioned(
          child: CircleAnimateWidget(
            sizeMin: maxCircleSize - 90,
            sizeMax: maxCircleSize - 60,
            duration: 1500,
            reverseDuration: 500,
            containerSize: maxCircleSize + 50,
          )
      )]
tnkciper

tnkciper1#

你可以像这样使用自定义的CustomPainter

class _TrueCallerPainter extends CustomPainter {
  _TrueCallerPainter(this.animation) : super(repaint: animation);

  final Animation<double> animation;

  final ringData = [
    (_curve(0.0), Colors.green.shade900.withOpacity(0.4)),
    (_curve(0.5), Colors.green.shade900.withOpacity(0.6)),
    (_curve(1.0), Colors.green.shade900),
  ];

  static Curve _curve(double t) {
    const delay = 0.25;
    return Interval(lerpDouble(delay, 0, t)!, lerpDouble(1, 1 - delay, t)!);
  }

  @override
  void paint(Canvas canvas, Size size) {
    // timeDilation = 10;
    final r = 0.85 * size.shortestSide / 2;
    final paint = Paint();
    double factor = 1;
    for (final (curve, color) in ringData) {
      final radius = r * factor - sin(curve.transform(animation.value) * 2 * pi) * r * 0.05;
      canvas.drawCircle(size.center(Offset.zero), radius, paint..color = color);
      factor -= 0.15;
    }
  }

  @override
  bool shouldRepaint(_TrueCallerPainter oldDelegate) => false;
}

并将其用于:

class TrueCaller extends StatefulWidget {
  @override
  State<TrueCaller> createState() => _TrueCallerState();
}

class _TrueCallerState extends State<TrueCaller> with TickerProviderStateMixin {
  late final controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 750));

  @override
  void initState() {
    super.initState();
    _loop();
  }

  _loop() async {
    int i = 0;
    while (i++ < 8) {
      await Future.delayed(const Duration(seconds: 1));
      await controller.forward(from: 0);
    }
  }

  @override
  Widget build(BuildContext context) {
    return DecoratedBox(
      decoration: const BoxDecoration(
        gradient: LinearGradient(colors: [Colors.black, Colors.black87]),
      ),
      child: CustomPaint(
        painter: _TrueCallerPainter(controller),
        child: SizedBox.expand(
          child: Transform.scale(
            scale: 0.5,
            child: const FittedBox(child: Icon(Icons.shield, color: Colors.black45)),
          ),
        ),
      ),
    );
  }
}

注意它使用dart v3.0,如果你有旧版本,用一些只包含两个数据字段的Ring类示例替换ringData的记录:CurveColor

编辑

如果你不想使用CustomPaint,你可以使用三个ScaleTransition

class TrueCaller extends StatefulWidget {
  @override
  State<TrueCaller> createState() => _TrueCallerState();
}

class _TrueCallerState extends State<TrueCaller> with TickerProviderStateMixin {
  late final controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 750));
  final _curves = [0.0, 0.5, 1.0].map(_curve).toList();

  @override
  void initState() {
    super.initState();
    _loop();
  }

  static Curve _curve(double t) {
    const delay = 0.25;
    return Interval(lerpDouble(delay, 0, t)!, lerpDouble(1, 1 - delay, t)!);
  }

  _scale(Curve c, double f, double t) => f - sin(c.transform(t) * 2 * pi) * 0.05;

  Widget _ring(Color color) => DecoratedBox(
    decoration: BoxDecoration(
      color: color,
      shape: BoxShape.circle,
    ),
  );

  _loop() async {
    int i = 0;
    while (i++ < 8) {
      await Future.delayed(const Duration(seconds: 1));
      await controller.forward(from: 0);
    }
  }

  @override
  Widget build(BuildContext context) {
    return DecoratedBox(
      decoration: const BoxDecoration(
        gradient: LinearGradient(colors: [Colors.black, Colors.black87]),
      ),
      child: Stack(
        fit: StackFit.expand,
        children: [
          ScaleTransition(
            scale: Animation.fromValueListenable(controller, transformer: (t) => _scale(_curves[0], 0.8, t)),
            child: _ring(Colors.green.withOpacity(0.25)),
          ),
          ScaleTransition(
            scale: Animation.fromValueListenable(controller, transformer: (t) => _scale(_curves[1], 0.7, t)),
            child: _ring(Colors.green.withOpacity(0.25)),
          ),
          ScaleTransition(
            scale: Animation.fromValueListenable(controller, transformer: (t) => _scale(_curves[2], 0.6, t)),
            child: _ring(Colors.green.shade800),
          ),
          Transform.scale(
            scale: 0.5,
            child: const FittedBox(child: Icon(Icons.shield, color: Colors.black45)),
          ),
        ],
      ),
    );
  }
}

相关问题