Flutter 3.x+Multi-Layer WavesCustomPainter & ClipPath

Flutter Wave Generator

Design flowing multi-layer organic waves, curved dividers, and animated liquid transitions with copy-ready Dart code.

Start
End
BG
Flutter CustomPainter Code
// Widget usage
// Dynamic organic animated waves
SizedBox(
  width: double.infinity,
  height: 300.0,
  child: const AnimatedWavesWidget(
    waveType: 'layered',
    layersCount: 3,
    amplitude: 40.0,
    frequency: 1.80,
    phase: 0.00,
    positionY: 150.0,
    smoothness: 0.35,
    isBottom: true,
    isGradient: true,
    primaryColor: const Color(0xFF8B5CF6),
    secondaryColor: const Color(0xFFEC4899),
  ),
)

// โ”€โ”€โ”€ Animated Waves Widget โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
import 'dart:math' as math;
import 'package:flutter/material.dart';

class AnimatedWavesWidget extends StatefulWidget {
  final double waveHeight;
  final String waveType;
  final int layersCount;
  final double amplitude;
  final double frequency;
  final double phase;
  final double positionY;
  final double smoothness;
  final bool isBottom;
  final bool isGradient;
  final Color primaryColor;
  final Color secondaryColor;

  const AnimatedWavesWidget({
    Key? key,
    this.waveHeight = 300.0,
    this.waveType = 'layered',
    this.layersCount = 3,
    this.amplitude = 40.0,
    this.frequency = 1.80,
    this.phase = 0.00,
    this.positionY = 150.0,
    this.smoothness = 0.35,
    this.isBottom = true,
    this.isGradient = true,
    this.primaryColor = const Color(0xFF8B5CF6),
    this.secondaryColor = const Color(0xFFEC4899),
  }) : super(key: key);

  @override
  State<AnimatedWavesWidget> createState() => _AnimatedWavesWidgetState();
}

class _AnimatedWavesWidgetState extends State<AnimatedWavesWidget> with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 5),
    )..repeat();
  }

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

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (context, child) {
        return CustomPaint(
          size: Size(double.infinity, widget.waveHeight),
          painter: WavePainter(
            time: _controller.value * math.pi * 2,
            waveType: widget.waveType,
            layersCount: widget.layersCount,
            amplitude: widget.amplitude,
            frequency: widget.frequency,
            phase: widget.phase,
            positionY: widget.positionY,
            smoothness: widget.smoothness,
            isBottom: widget.isBottom,
            isGradient: widget.isGradient,
            primaryColor: widget.primaryColor,
            secondaryColor: widget.secondaryColor,
          ),
        );
      },
    );
  }
}

class WavePainter extends CustomPainter {
  final double time;
  final String waveType;
  final int layersCount;
  final double amplitude;
  final double frequency;
  final double phase;
  final double positionY;
  final double smoothness;
  final bool isBottom;
  final bool isGradient;
  final Color primaryColor;
  final Color secondaryColor;

  WavePainter({
    required this.time,
    required this.waveType,
    required this.layersCount,
    required this.amplitude,
    required this.frequency,
    required this.phase,
    required this.positionY,
    required this.smoothness,
    required this.isBottom,
    required this.isGradient,
    required this.primaryColor,
    required this.secondaryColor,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final W = size.width;
    final H = size.height;
    
    // Scale baseline 300 units to layout dimensions
    final scaleY = H / 300.0;
    final scaleX = W / 300.0;
    final boundaryY = isBottom ? H : 0.0;

    for (int layer = 0; layer < layersCount; layer++) {
      final amp = amplitude * (1 - layer * 0.15) * scaleY;
      final freq = frequency * (1 + layer * 0.1);
      final ph = phase + layer * 1.5 + time * (1 + layer * 0.15);
      final posY = (positionY + (isBottom ? layer * 15 : -layer * 15)) * scaleY;

      final int numPoints = waveType == 'curved' ? 6 : 20;
      final List<Offset> pts = [];

      for (int i = 0; i < numPoints; i++) {
        final x = (i * 300.0) / (numPoints - 1) * scaleX;
        double y = posY;
        if (waveType == 'liquid') {
          y += math.sin(x * (freq / W * math.pi * 2) + ph) * amp * 0.7;
          y += math.cos(x * (freq * 2.0 / W * math.pi * 2) - ph * 0.5) * amp * 0.3;
        } else {
          y += math.sin(x * (freq / W * math.pi * 2) + ph) * amp;
        }
        pts.add(Offset(x, y.clamp(0.0, H)));
      }

      final path = Path();
      path.moveTo(0.0, boundaryY);
      path.lineTo(pts[0].dx, pts[0].dy);

      final tension = waveType == 'sine' ? 0.35 : smoothness;

      for (int i = 0; i < numPoints - 1; i++) {
        final prev = pts[i == 0 ? 0 : i - 1];
        final curr = pts[i];
        final next = pts[i + 1];
        final next2 = pts[i + 2 >= numPoints ? numPoints - 1 : i + 2];

        final c1 = Offset(
          curr.dx + (next.dx - prev.dx) * tension,
          curr.dy + (next.dy - prev.dy) * tension,
        );
        final c2 = Offset(
          next.dx - (next2.dx - curr.dx) * tension,
          next.dy - (next2.dy - curr.dy) * tension,
        );

        path.cubicTo(c1.dx, c1.dy, c2.dx, c2.dy, next.dx, next.dy);
      }
      path.lineTo(W, boundaryY);
      path.close();

      final opacity = (1.0 - (layer * 0.2)).clamp(0.1, 1.0);
      final layerColor1 = primaryColor.withOpacity(primaryColor.opacity * opacity);
      final layerColor2 = secondaryColor.withOpacity(secondaryColor.opacity * opacity);

      final paint = Paint()..style = PaintingStyle.fill;

      if (isGradient) {
        paint.shader = LinearGradient(
          begin: Alignment.topCenter,
          end: Alignment.bottomCenter,
          colors: [layerColor1, layerColor2],
        ).createShader(Rect.fromLTRB(0, posY - amp, W, boundaryY));
      } else {
        paint.color = layerColor1;
      }

      canvas.drawPath(path, paint);
    }
  }

  @override
  bool shouldRepaint(covariant WavePainter oldDelegate) => true;
}
Animated waves use single state AnimationControllers to render high performance floating wave shapes.
Flutter Generator Suite SwitcherView All Tools Suite โ†’