本文同步发表于我的微信公众号,微信搜索 程语新视界 即可关注,每个工作日都有文章更新

一、Flutter动画分类

1.1 基于实现方式分类

// 1. 隐式动画(Implicit Animation)
AnimatedContainer(
  duration: Duration(seconds: 1),
  width: _isExpanded ? 200 : 100,
  height: _isExpanded ? 200 : 100,
  color: _isSelected ? Colors.blue : Colors.grey,
)

// 2. 显式动画(Explicit Animation)
AnimationController(
  duration: Duration(seconds: 2),
  vsync: this,
)

1.2 基于动画类型分类

  • 补间动画(Tween Animation):在两个值之间过渡

  • 物理动画(Physics-based Animation):模拟物理效果

  • 交错动画(Staggered Animation):多个动画按顺序执行

  • 自定义动画(Custom Animation):使用CustomPainter创建

二、核心动画组件

2.1 AnimationController - 动画控制器

class _MyAnimationState extends State<MyAnimation> 
    with SingleTickerProviderStateMixin {
  
  late AnimationController _controller;
  
  @override
  void initState() {
    super.initState();
    
    // 创建AnimationController
    _controller = AnimationController(
      duration: Duration(seconds: 2),      // 动画时长
      vsync: this,                         // 垂直同步,防止屏幕外动画消耗资源
      lowerBound: 0.0,                     // 最小值
      upperBound: 1.0,                     // 最大值
    );
    
    // 添加状态监听
    _controller.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        // 动画完成时的操作
        _controller.reverse();
      } else if (status == AnimationStatus.dismissed) {
        // 动画消失时的操作
        _controller.forward();
      }
    });
  }
  
  @override
  void dispose() {
    _controller.dispose();  // 必须释放资源
    super.dispose();
  }
}

2.2 Tween - 补间动画

// 创建补间动画
Animation<double> _animation;
Animation<Color?> _colorAnimation;
Animation<BorderRadius?> _borderRadiusAnimation;

@override
void initState() {
  super.initState();
  
  // 创建颜色补间动画
  _colorAnimation = ColorTween(
    begin: Colors.red,
    end: Colors.blue,
  ).animate(_controller);
  
  // 创建圆角补间动画
  _borderRadiusAnimation = BorderRadiusTween(
    begin: BorderRadius.circular(0),
    end: BorderRadius.circular(50),
  ).animate(_controller);
  
  // 创建多个属性的补间动画
  _animation = Tween<double>(begin: 0, end: 300).animate(
    CurvedAnimation(
      parent: _controller,
      curve: Curves.easeInOut,  // 动画曲线
    ),
  );
}

2.3 AnimationBuilder - 动画构建器

AnimatedBuilder(
  animation: _controller,
  builder: (context, child) {
    return Transform.rotate(
      angle: _controller.value * 2 * pi,  // 旋转动画
      child: Container(
        width: 100,
        height: 100,
        color: Colors.blue,
      ),
    );
  },
)

三、隐式动画组件

3.1 常用隐式动画组件

// 1. AnimatedContainer - 容器动画
AnimatedContainer(
  duration: Duration(milliseconds: 300),
  curve: Curves.easeInOut,
  width: _isExpanded ? 200 : 100,
  height: _isExpanded ? 200 : 100,
  color: _color,
  alignment: _alignment,
  padding: _padding,
  margin: _margin,
  transform: _transform,
  child: Text('Animated Container'),
)

// 2. AnimatedOpacity - 透明度动画
AnimatedOpacity(
  duration: Duration(seconds: 1),
  opacity: _visible ? 1.0 : 0.0,
  child: Container(
    width: 200,
    height: 200,
    color: Colors.blue,
  ),
)

// 3. AnimatedPositioned - 位置动画(在Stack中使用)
Stack(
  children: [
    AnimatedPositioned(
      duration: Duration(seconds: 1),
      left: _isRight ? 100 : 0,
      top: _isBottom ? 100 : 0,
      child: Container(
        width: 100,
        height: 100,
        color: Colors.red,
      ),
    ),
  ],
)

// 4. AnimatedAlign - 对齐动画
AnimatedAlign(
  duration: Duration(seconds: 1),
  alignment: _alignment,
  child: Container(
    width: 100,
    height: 100,
    color: Colors.green,
  ),
)

// 5. AnimatedPadding - 内边距动画
AnimatedPadding(
  duration: Duration(seconds: 1),
  padding: _hasPadding ? EdgeInsets.all(20) : EdgeInsets.zero,
  child: Container(
    color: Colors.orange,
    child: Text('Padding Animation'),
  ),
)

// 6. AnimatedDefaultTextStyle - 文本样式动画
AnimatedDefaultTextStyle(
  duration: Duration(seconds: 1),
  style: _isBigText 
      ? TextStyle(fontSize: 30, color: Colors.red)
      : TextStyle(fontSize: 14, color: Colors.black),
  child: Text('Text Style Animation'),
)

// 7. AnimatedCrossFade - 交叉淡入淡出
AnimatedCrossFade(
  duration: Duration(seconds: 1),
  firstChild: Container(
    width: 200,
    height: 200,
    color: Colors.blue,
    child: Center(child: Text('First')),
  ),
  secondChild: Container(
    width: 200,
    height: 100,
    color: Colors.green,
    child: Center(child: Text('Second')),
  ),
  crossFadeState: _showFirst 
      ? CrossFadeState.showFirst 
      : CrossFadeState.showSecond,
)

// 8. AnimatedSwitcher - 切换动画
AnimatedSwitcher(
  duration: Duration(seconds: 1),
  transitionBuilder: (child, animation) {
    return ScaleTransition(
      scale: animation,
      child: child,
    );
  },
  child: _currentWidget,
)

四、显式动画流程

class ExplicitAnimationExample extends StatefulWidget {
  @override
  _ExplicitAnimationExampleState createState() => _ExplicitAnimationExampleState();
}

class _ExplicitAnimationExampleState extends State<ExplicitAnimationExample> 
    with SingleTickerProviderStateMixin {
  
  late AnimationController _controller;
  late Animation<double> _animation;
  late Animation<Color?> _colorAnimation;
  
  @override
  void initState() {
    super.initState();
    
    // 1. 创建控制器
    _controller = AnimationController(
      duration: Duration(seconds: 2),
      vsync: this,
    );
    
    // 2. 创建动画曲线
    final CurvedAnimation curvedAnimation = CurvedAnimation(
      parent: _controller,
      curve: Curves.easeInOut,
    );
    
    // 3. 创建补间动画
    _animation = Tween<double>(
      begin: 0,
      end: 300,
    ).animate(curvedAnimation);
    
    // 4. 创建颜色动画
    _colorAnimation = ColorTween(
      begin: Colors.red,
      end: Colors.blue,
    ).animate(curvedAnimation);
    
    // 5. 添加状态监听
    _controller.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        _controller.reverse();
      } else if (status == AnimationStatus.dismissed) {
        _controller.forward();
      }
    });
    
    // 6. 启动动画
    _controller.forward();
  }
  
  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (context, child) {
        return Center(
          child: Container(
            width: _animation.value,
            height: _animation.value,
            color: _colorAnimation.value,
            child: Center(
              child: Text(
                '${(_controller.value * 100).round()}%',
                style: TextStyle(color: Colors.white),
              ),
            ),
          ),
        );
      },
    );
  }
  
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

五、自定义动画

5.1 使用CustomPainter创建自定义动画

class ParticleSystemPainter extends CustomPainter {
  final List<Particle> particles;
  final double time;
  
  ParticleSystemPainter(this.particles, this.time);
  
  @override
  void paint(Canvas canvas, Size size) {
    for (var particle in particles) {
      // 计算粒子位置
      final x = particle.x + particle.vx * time;
      final y = particle.y + particle.vy * time - 0.5 * 9.8 * time * time;
      
      // 绘制粒子
      final paint = Paint()
        ..color = particle.color.withOpacity(1 - time / 2)
        ..style = PaintingStyle.fill;
      
      canvas.drawCircle(
        Offset(x, y),
        particle.radius * (1 - time / 3),
        paint,
      );
    }
  }
  
  @override
  bool shouldRepaint(covariant ParticleSystemPainter oldDelegate) {
    return particles != oldDelegate.particles || time != oldDelegate.time;
  }
}

class Particle {
  final double x, y;
  final double vx, vy;
  final double radius;
  final Color color;
  
  Particle(this.x, this.y, this.vx, this.vy, this.radius, this.color);
}

class ParticleAnimation extends StatefulWidget {
  @override
  _ParticleAnimationState createState() => _ParticleAnimationState();
}

class _ParticleAnimationState extends State<ParticleAnimation> 
    with SingleTickerProviderStateMixin {
  
  late AnimationController _controller;
  late List<Particle> particles;
  
  @override
  void initState() {
    super.initState();
    
    particles = List.generate(50, (index) {
      return Particle(
        Random().nextDouble() * 300,
        Random().nextDouble() * 300,
        Random().nextDouble() * 10 - 5,
        Random().nextDouble() * 20 - 10,
        Random().nextDouble() * 5 + 2,
        Colors.primaries[Random().nextInt(Colors.primaries.length)],
      );
    });
    
    _controller = AnimationController(
      duration: Duration(seconds: 3),
      vsync: this,
    )..repeat();
  }
  
  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (context, child) {
        return CustomPaint(
          painter: ParticleSystemPainter(particles, _controller.value),
          size: Size(300, 300),
        );
      },
    );
  }
}

六、代码是例

旋转加载动画

class LoadingSpinner extends StatefulWidget {
  final Color color;
  final double size;
  
  const LoadingSpinner({
    Key? key,
    this.color = Colors.blue,
    this.size = 50,
  }) : super(key: key);
  
  @override
  _LoadingSpinnerState createState() => _LoadingSpinnerState();
}

class _LoadingSpinnerState extends State<LoadingSpinner> 
    with SingleTickerProviderStateMixin {
  
  late AnimationController _controller;
  
  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: Duration(seconds: 1),
      vsync: this,
    )..repeat();
  }
  
  @override
  Widget build(BuildContext context) {
    return RotationTransition(
      turns: _controller,
      child: Container(
        width: widget.size,
        height: widget.size,
        decoration: BoxDecoration(
          color: widget.color,
          shape: BoxShape.circle,
        ),
        child: CustomPaint(
          painter: _SpinnerPainter(widget.color),
        ),
      ),
    );
  }
  
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

class _SpinnerPainter extends CustomPainter {
  final Color color;
  
  _SpinnerPainter(this.color);
  
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = color.withOpacity(0.3)
      ..strokeWidth = 4
      ..style = PaintingStyle.stroke;
    
    canvas.drawCircle(
      Offset(size.width / 2, size.height / 2),
      size.width / 2 - 2,
      paint,
    );
    
    final gradientPaint = Paint()
      ..shader = SweepGradient(
        colors: [Colors.transparent, color],
        stops: [0.0, 0.75],
        startAngle: 0,
        endAngle: pi * 1.5,
      ).createShader(Rect.fromCircle(
        center: Offset(size.width / 2, size.height / 2),
        radius: size.width / 2 - 2,
      ))
      ..strokeWidth = 4
      ..style = PaintingStyle.stroke
      ..strokeCap = StrokeCap.round;
    
    canvas.drawArc(
      Rect.fromCircle(
        center: Offset(size.width / 2, size.height / 2),
        radius: size.width / 2 - 2,
      ),
      0,
      pi * 1.5,
      false,
      gradientPaint,
    );
  }
  
  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐