前言
在移动应用开发中,精美的加载动画不仅能提升用户体验,还能有效缓解用户等待过程中的焦虑感。今天要分享的是一个基于 Flutter CustomPaint 实现的流体液态加载动画,它通过模拟液体流动、波浪起伏,并添加气泡和光晕效果,打造出一个极具视觉冲击力的加载指示器。
技术要点
- CustomPaint 与 CustomPainter 的应用
- 贝塞尔曲线绘制平滑波浪
- 动画控制与状态管理
- 手势交互实现
- 粒子效果(气泡)与光晕处理
效果实现
1. 页面结构设计
首先创建一个基础页面,包含动画显示区域和交互控件:
            
            
              dart
              
              
            
          
          class LiquidLoaderPage extends StatefulWidget {
  @override
  _LiquidLoaderPageState createState() => _LiquidLoaderPageState();
}
class _LiquidLoaderPageState extends State<LiquidLoaderPage>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  double _progress = 0.0;
  final _dragHeight = 100.0;
  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: 3000),
    )..addListener(() {
        setState(() {
          _progress = _controller.value;
        });
      });
    _controller.repeat();
  }
  
  // 其他代码省略...
}这里使用 AnimationController 控制动画进度,将动画设为循环模式(repeat),动画时长为3秒。
2. 布局设计
            
            
              dart
              
              
            
          
          @override
Widget build(BuildContext context) {
  return Scaffold(
    backgroundColor: Colors.black,
    appBar: AppBar(
      title: Text('流体液态加载动画'),
      backgroundColor: Colors.black87,
      elevation: 0,
    ),
    body: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Container(
            width: 300,
            height: 300,
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(20),
              boxShadow: [
                BoxShadow(
                  color: Colors.blue.withOpacity(0.5),
                  blurRadius: 20,
                  spreadRadius: 5,
                )
              ],
            ),
            child: ClipRRect(
              borderRadius: BorderRadius.circular(20),
              child: CustomPaint(
                painter: LiquidPainter(
                  progress: _progress,
                  color1: Colors.blue,
                  color2: Colors.purple,
                ),
              ),
            ),
          ),
          SizedBox(height: 50),
          GestureDetector(
            onVerticalDragUpdate: (details) {
              setState(() {
                _progress -= details.delta.dy / _dragHeight;
                _progress = _progress.clamp(0.0, 1.0);
              });
              // 暂停自动动画
              _controller.stop();
            },
            onVerticalDragEnd: (details) {
              // 恢复自动动画
              _controller.repeat();
            },
            child: Container(
              width: 200,
              height: 60,
              decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(30),
                gradient: LinearGradient(
                  colors: [Colors.blue, Colors.purple],
                ),
                boxShadow: [
                  BoxShadow(
                    color: Colors.blue.withOpacity(0.3),
                    blurRadius: 10,
                    spreadRadius: 2,
                  ),
                ],
              ),
              child: Center(
                child: Text(
                  '拖动调整液位',
                  style: TextStyle(
                    color: Colors.white,
                    fontSize: 18,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ),
            ),
          ),
        ],
      ),
    ),
  );
}布局设计要点:
- 以深色背景为基础,营造出高科技感
- 为动画容器添加圆角和阴影,增强立体感
- 底部添加一个可交互按钮,让用户通过拖拽来控制液位高度
- 使用渐变色和阴影增强视觉效果
3. 核心绘制代码:CustomPainter 实现
            
            
              dart
              
              
            
          
          class LiquidPainter extends CustomPainter {
  final double progress;
  final Color color1;
  final Color color2;
  LiquidPainter({
    required this.progress,
    required this.color1,
    required this.color2,
  });
  @override
  void paint(Canvas canvas, Size size) {
    // 绘制背景
    final backgroundPaint = Paint()..color = Colors.black87;
    canvas.drawRect(
        Rect.fromLTWH(0, 0, size.width, size.height), backgroundPaint);
    // 绘制液体
    final liquidHeight = size.height * progress;
    final liquidRect =
        Rect.fromLTWH(0, size.height - liquidHeight, size.width, liquidHeight);
    // 创建液体渐变
    final gradient = LinearGradient(
      begin: Alignment.topCenter,
      end: Alignment.bottomCenter,
      colors: [color1, color2],
    );
    final liquidPaint = Paint()..shader = gradient.createShader(liquidRect);
    // 创建液体波浪路径
    final path = Path();
    
    // 省略部分代码...
  }
  
  // 其他方法省略...
}4. 波浪效果实现
波浪效果是这个动画的精髓,通过贝塞尔曲线和三角函数来实现:
            
            
              dart
              
              
            
          
          // 使用平滑的贝塞尔曲线绘制波浪
final waveHeight = 6.0; // 减小波浪高度
final baseHeight = size.height - liquidHeight;
// 先添加第一个点
path.lineTo(0, baseHeight);
// 使用更多的点和quadraticBezierTo绘制平滑曲线
final segments = 16;
double previousX = 0;
double previousY = baseHeight;
for (int i = 1; i <= segments; i++) {
  final t = i / segments;
  final x = size.width * t;
  // 使用单一的正弦函数,避免尖锐的波峰
  final sinValue = sin((progress * 2 * pi) + (t * 4 * pi));
  final y = baseHeight + sinValue * waveHeight;
  // 控制点,在前一点和当前点之间
  final controlX = (previousX + x) / 2;
  final controlY = baseHeight +
      sin((progress * 2 * pi) + ((t - 0.5 / segments) * 4 * pi)) *
          waveHeight;
  // 使用二次贝塞尔曲线连接点
  path.quadraticBezierTo(controlX, controlY, x, y);
  previousX = x;
  previousY = y;
}
// 右边界和底部
path.lineTo(size.width, size.height - liquidHeight);
path.lineTo(size.width, size.height);
path.close();
// A绘制液体
canvas.drawPath(path, liquidPaint);这段代码的关键点:
- 将波浪线分为16个段落,确保曲线平滑
- 使用正弦函数计算每个点的y坐标,实现波浪效果
- 通过动画控制器的progress值来驱动正弦函数,使波浪流动起来
- 使用quadraticBezierTo绘制二次贝塞尔曲线,连接各个点,让波浪看起来更加自然
5. 气泡效果实现
为增强液体的真实感,添加随机气泡效果:
            
            
              dart
              
              
            
          
          void drawBubbles(Canvas canvas, Size size, Rect liquidRect) {
  final random = Random(progress.toInt() * 1000);
  final bubblePaint = Paint()..color = Colors.white.withOpacity(0.5);
  for (int i = 0; i < 15; i++) {
    final bubbleSize = random.nextDouble() * 8 + 2;
    final bubbleX = random.nextDouble() * size.width;
    final bubbleY =
        size.height - (random.nextDouble() * liquidRect.height * 0.8);
    final offset = sin((progress * 2 * pi) + i) * 5;
    canvas.drawCircle(
        Offset(bubbleX, bubbleY + offset), bubbleSize, bubblePaint);
  }
}气泡效果的实现要点:
- 使用Random生成随机位置和大小的气泡
- 通过progress播放进度作为随机种子,使气泡位置在动画不同阶段保持一致
- 添加正弦函数偏移,让气泡上下浮动,增强液体流动感
6. 光晕效果实现
为提升视觉质感,添加光晕和高光效果:
            
            
              dart
              
              
            
          
          void drawGlow(Canvas canvas, Size size, Path path, Paint paint) {
  // 添加发光效果
  for (int i = 0; i < 5; i++) {
    final glowPaint = Paint()
      ..color = color1.withOpacity(0.1 - i * 0.02)
      ..style = PaintingStyle.stroke
      ..strokeWidth = i * 2.0;
    canvas.drawPath(path, glowPaint);
  }
  // 添加顶部高光
  final highlightPaint = Paint()
    ..color = Colors.white.withOpacity(0.3)
    ..style = PaintingStyle.stroke
    ..strokeWidth = 1.0;
  final highlightPath = Path();
  final baseHeight = size.height - size.height * progress;
  // 省略部分代码...
  canvas.drawPath(highlightPath, highlightPaint);
}光晕效果的技术要点:
- 通过多次绘制轮廓线,并逐渐降低透明度,营造出光晕扩散效果
- 在液面顶部添加白色高光,增强液体光泽感
- 高光同样使用贝塞尔曲线,保持与波浪形状一致
交互性实现
这个动画不仅视觉效果出色,还具备交互功能。通过垂直拖动可以调整液位高度:
            
            
              dart
              
              
            
          
          GestureDetector(
  onVerticalDragUpdate: (details) {
    setState(() {
      _progress -= details.delta.dy / _dragHeight;
      _progress = _progress.clamp(0.0, 1.0);
    });
    // 暂停自动动画
    _controller.stop();
  },
  onVerticalDragEnd: (details) {
    // 恢复自动动画
    _controller.repeat();
  },
  // ...
)交互实现重点:
- 通过GestureDetector捕获垂直拖动手势
- 根据拖动距离计算progress值,实现液位调整
- 拖动时暂停自动动画,拖动结束后恢复动画
- 使用clamp函数确保progress值在0.0到1.0之间
性能优化
在实现这类复杂动画时,需要注意以下性能优化点:
- 减少不必要的重绘:通过shouldRepaint方法只在progress变化时才重绘
            
            
              dart
              
              
            
          
          @override
bool shouldRepaint(covariant LiquidPainter oldDelegate) {
  return oldDelegate.progress != progress;
}- 优化Path构建:使用适当的segments数量,既保证平滑度,又不过度消耗性能
- 减小波浪和气泡数量:根据设备性能调整气泡数量和波浪分段数
- 避免透明度叠加:谨慎使用半透明效果,过多的透明度叠加会导致渲染性能下降
小结
这个流体液态加载动画通过CustomPaint和数学曲线,实现了一个既美观又实用的加载指示器。关键技术包括:
- 贝塞尔曲线绘制波浪效果
- 三角函数控制液体流动
- 随机函数生成气泡效果
- 多层绘制实现光晕和高光
- 手势交互增强用户体验
通过这个案例,我们可以看到Flutter强大的自定义绘制能力。灵活运用CustomPaint,配合数学函数和动画控制器,可以实现各种复杂而精美的动画效果。
希望这篇文章能帮助开发者更好地理解和应用Flutter的绘制系统,创造出更加精美的用户界面和动画效果。