[Flutter] Implementation of jumping animation in place

1. Animation code

import 'package:flutter/material.dart';

//提供给父组件使用的key
GlobalKey<_Capriole> poltWaitKey = GlobalKey();

/* 原地跳跃动画 */
class Capriole extends StatefulWidget {
  final double width;
  final double height;
  final Widget fillWidget; // 填充组件
  final bool autoPlay; // 是否自动播放
  final int animationPeriod; // 动画播放周期(影响动画速度)
  final bool showShadow; // 是否显示阴影
  final double shadowSize; // 阴影初始大小
  Capriole(
      {Key key,
      this.width = 80,
      this.height = 160,
      this.fillWidget,
      this.autoPlay = true,
      this.animationPeriod = 2000,
      this.showShadow = true,
      this.shadowSize = 0.7})
      : super(key: key);

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

class _Capriole extends State<Capriole> with TickerProviderStateMixin {
  AnimationController _animationController;
  Animation<double> _animation;

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

    /// AnimationController在给定的时间段内线性的生成从0.0到1.0(默认区间)的数字,总时长
    _animationController = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: widget.animationPeriod),
    );

    Animation curveAnimation =
        new CurvedAnimation(parent: _animationController, curve: Curves.ease);

    _animation = Tween<double>(begin: 0.0, end: 1.0).animate(curveAnimation);

    _animationController.addListener(() {
      setState(() {});
    });

    if (widget.autoPlay) {
      this.repeatAnimation();
    }
  }

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

  @override
  Widget build(BuildContext context) {
    double radius = widget.width / 2; //运动物体半径
    double ovalWidth = widget.width * 0.8; //阴影(椭圆)宽度
    double ovalHeight = ovalWidth / 2; //阴影(椭圆)高度
    double moveDistance =
        (widget.height - radius * 2) * 2 - ovalHeight; //运动物体移动距离
    double progress = _animation.value;
    double dropLen = moveDistance * progress;
    double riseLen = moveDistance * (1 - progress);

    return Container(
      width: widget.width,
      height: widget.height,
      child: CustomPaint(
        painter: widget.showShadow
            ? _ShowdowPainter(progress, widget.shadowSize)
            : null,
        child: Column(
          children: <Widget>[
            Transform.translate(
              offset: Offset(0.0, progress < 0.5 ? dropLen : riseLen),
              child: Container(
                alignment: Alignment.center,
                width: widget.width,
                height: widget.width,
                child: widget.fillWidget ??
                    ClipOval(
                        child: Container(
                      alignment: Alignment.center,
                      color: Colors.green,
                      width: 100,
                      height: 100,
                      child: Text(
                        'Fill',
                        style: TextStyle(fontSize: 20, color: Colors.white),
                      ),
                    )),
              ),
            ),
          ],
        ),
      ),
    );
  }

  /// 开始动画(一次)
  startAnimation() {
    _animationController.reset();
    _animationController.forward();
  }

  /// 循环动画
  repeatAnimation() {
    _animationController.reset();
    _animationController.repeat();
  }
}

class _ShowdowPainter extends CustomPainter {
  Paint viewPaint;
  Path path;
  final double progress;
  final double shadowSize;

  _ShowdowPainter(this.progress, this.shadowSize) {
    viewPaint = Paint()
      ..strokeCap = StrokeCap.round
      ..isAntiAlias = true
      ..color = Colors.black
      ..style = PaintingStyle.fill
      ..strokeWidth = 5.0;

    path = Path();
  }

  @override
  void paint(Canvas canvas, Size size) {
    Color paint2Color = Colors.white;
    double width = size.width; //运动物体宽度
    double height = size.height; //运动物体高度
    double ovalWidth = width * shadowSize; //阴影(椭圆)宽度
    double ovalHeight = ovalWidth / 2; //阴影(椭圆)高度
    double topDistance = height - ovalHeight; //阴影(椭圆)与顶部距离
    if (progress < 0.5) {
      paint2Color = Color.lerp(Colors.black12, Colors.black45, progress);
    } else {
      paint2Color = Color.lerp(Colors.black45, Colors.black12, progress);
    }

    Paint paint2 = Paint()
      ..isAntiAlias = true
      ..color = paint2Color
      ..strokeCap = StrokeCap.round
      ..strokeWidth = 10
      ..style = PaintingStyle.fill;

    if (progress < 0.5) {
      canvas.drawOval(
          new Rect.fromLTWH(
              (width - (ovalWidth - (ovalWidth * progress))) / 2,
              topDistance + (ovalHeight * progress / 2),
              ovalWidth - (ovalWidth * progress),
              ovalHeight - (ovalHeight * progress)),
          paint2);
    } else {
      canvas.drawOval(
          new Rect.fromLTWH(
              (width - (ovalWidth * progress)) / 2,
              topDistance + (ovalHeight - (ovalHeight * progress)) / 2,
              ovalWidth * progress,
              ovalHeight * progress),
          paint2);
    }
  }

  /// 如果绘制依赖外部状态,那么我们就应该在shouldRepaint中判断依赖的状态是否改变,
  /// 如果已改变则应返回true来重绘,反之则应返回false不需要重绘。
  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}

2. Use

import 'package:flutter/material.dart';
import '../../compontents/animation/capriole.dart';

class Demo extends StatefulWidget {
  Demo({Key key}) : super(key: key);

  @override
  State<Demo> createState() => _DemoState();
}

class _DemoState extends State<Demo> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Container(
      margin: EdgeInsets.all(40),
      child: Wrap(spacing: 20, children: <Widget>[
        Capriole(),
        Capriole(
            fillWidget: ClipRRect(
          borderRadius: BorderRadius.circular(10),
          child: Container(
            color: Colors.green,
            width: 60,
            height: 60,
            child: Icon(
              Icons.android,
              size: 30,
              color: Colors.white,
            ),
          ),
        )),
        Capriole(
            animationPeriod: 1000,
            showShadow: false,
            fillWidget: Image.network(
                'https://img-home.csdnimg.cn/images/20201124032511.png')),
        Capriole(
            width: 200,
            height: 400,
            fillWidget: ClipOval(
                child: Container(
              alignment: Alignment.center,
              color: Colors.orange,
              width: 200,
              height: 200,
              child: Text(
                'Huge',
                style: TextStyle(fontSize: 20, color: Colors.white),
              ),
            ))),
      ]),
    ));
  }
}

3. Animation Demonstration

 

Guess you like

Origin blog.csdn.net/weixin_45731252/article/details/126508764