Photo Ripple Banner

flutter_ripple_effect 🌊

Pub Version Flutter Dart Performance License: MIT

A high-performance Flutter widget for creating interactive liquid ripple effects on images and widgets using custom GLSL shaders.


🚀 Features

  • 💧 Realistic Liquid Simulation: Physics-inspired ripple wave propagation powered by custom GLSL fragment shaders.
  • ⚡ 60 FPS Performance: Hardware-accelerated rendering on mobile, web, and desktop without third-party animation libraries.
  • 👆 Interactive & Gesture-Ready: Tap or drag to generate fluid wave interference.
  • 🎮 Programmatic Control: Trigger or clear waves on demand with RippleController.
  • 🖼️ Flexible Image Providers: Seamlessly works with AssetImage, NetworkImage, MemoryImage, FileImage, or raw ui.Image.
  • 🔋 Battery Efficient: Intelligent rendering stops running animation frames when there are no active ripples.

📸 Demo

Screenshot Animation Demo

📦 Getting Started

Add flutter_ripple_effect to your pubspec.yaml:

flutter pub add flutter_ripple_effect

or add it directly:

dependencies:
  flutter_ripple_effect: ^1.0.0

Import it in your Dart code:

import 'package:flutter_ripple_effect/flutter_ripple_effect.dart';

💻 Usage

1. Basic Example

Wrap your image with RippleEffect:

import 'package:flutter/material.dart';
import 'package:flutter_ripple_effect/flutter_ripple_effect.dart';

class MyRippleWidget extends StatelessWidget {
  const MyRippleWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return Center(
      child: RippleEffect(
        imageProvider: const AssetImage('assets/my_photo.jpg'),
        borderRadius: BorderRadius.circular(20),
        power: 1.2,
      ),
    );
  }
}

2. Programmatic Control (RippleController)

Trigger ripples from external events, buttons, or sensor inputs:

class ControlledRippleExample extends StatefulWidget {
  const ControlledRippleExample({super.key});

  @override
  State<ControlledRippleExample> createState() => _ControlledRippleExampleState();
}

class _ControlledRippleExampleState extends State<ControlledRippleExample> {
  final RippleController _controller = RippleController();

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

  void _triggerCenterWave() {
    // Add ripple at normalized coordinates (0.5, 0.5) with power 1.5
    _controller.addRipple(const Offset(0.5, 0.5), power: 1.5);
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        RippleEffect(
          controller: _controller,
          imageProvider: const NetworkImage('https://picsum.photos/600/600'),
          borderRadius: BorderRadius.circular(16),
        ),
        ElevatedButton(
          onPressed: _triggerCenterWave,
          child: const Text('Drop Ripple'),
        ),
        TextButton(
          onPressed: () => _controller.clear(),
          child: const Text('Clear Ripples'),
        ),
      ],
    );
  }
}

3. Low-level Canvas Painting (RipplePainter)

If you manage your own custom canvas render loop:

CustomPaint(
  painter: RipplePainter(
    program: fragmentProgram, // ui.FragmentProgram
    image: myUiImage,         // ui.Image
    ripples: activeRipples,   // List<RippleModel>
    time: elapsedTimeSeconds, // double
  ),
  size: Size(width, height),
)

⚙️ Properties

Property Type Default Description
imageProvider ImageProvider? null Image provider (AssetImage, NetworkImage, etc.).
image ui.Image? null Directly pass a decoded ui.Image.
controller RippleController? null Controller for programmatic trigger & clear actions.
power double 1.0 Default ripple distortion power.
maxRipples int 5 Maximum concurrent ripples supported (up to 5).
rippleDuration Duration 2500ms Duration each ripple persists before fading out.
enableTouch bool true Whether user taps/touches generate ripples.
onTap Function(Offset, Offset)? null Callback giving (normalizedPos, localPos).
borderRadius BorderRadius? null Border radius for clipping the effect.
placeholder WidgetBuilder? null Widget displayed while shader or image is loading.
errorBuilder Widget Function(context, error)? null Widget displayed if asset loading fails.

🛠️ Example App

Check out the full example in the example/ directory for a complete demo project.


📄 License

MIT License - see the LICENSE file for details.

Libraries

flutter_ripple_effect
A high-performance Flutter package for creating interactive liquid ripple effects on images and widgets using custom GLSL shaders.