image

10 Jan 2026

9K

35K

Flutter Image Reveal Animation with ClipPath

Dynamic and engaging user interfaces are crucial for modern applications. Flutter, with its powerful rendering engine, offers immense flexibility to create stunning animations. One such captivating effect is the "Image Reveal" animation, where an image progressively appears, often from a central point, giving a sense of discovery or focus. This article will guide you through implementing a sophisticated image reveal animation in Flutter using ClipPath and CustomClipper, driven by AnimationController and Tween.

Understanding the Core Concepts

Before diving into the code, let's briefly understand the key components:

  1. ClipPath: A Flutter widget that clips its child using a path. It's incredibly versatile for creating non-rectangular shapes.
  2. CustomClipper: An abstract class that allows you to define a custom clipping path. You override getClip to draw your path and shouldReclip to specify when the clipper needs to redraw (e.g., when animation values change).
  3. AnimationController: Manages an animation. It can be started, stopped, forward, reversed, and set to loop. It provides values from 0.0 to 1.0 over a specified duration.
  4. Tween: Defines a range of values over which an animation should interpolate. For instance, a Tween(begin: 0.0, end: 1.0) will animate from 0.0 to 1.0.
  5. AnimatedBuilder: A widget that rebuilds its child in response to an Animation changing value. It's efficient because it only rebuilds the parts of the widget tree that depend on the animation, leaving the rest of the widget subtree intact.

Implementation Steps

Let's build a circular image reveal animation step-by-step.

1. Create a CustomClipper for the Reveal Effect

We need a clipper that defines a circle whose radius changes based on an animation value. This clipper will take a revealPercent value (from 0.0 to 1.0) to determine the current size of the circle.


import 'package:flutter/material.dart';
import 'dart:math' as math;

class CircleRevealClipper extends CustomClipper {
  final double revealPercent;

  CircleRevealClipper(this.revealPercent);

  @override
  Path getClip(Size size) {
    final center = Offset(size.width / 2, size.height / 2);
    // Calculate the maximum possible radius needed to cover the entire rectangle
    final maxRadius = math.sqrt(size.width * size.width + size.height * size.height) / 2;
    // Scale the maxRadius by revealPercent to get the current radius
    final radius = maxRadius * revealPercent;
    
    return Path()..addOval(Rect.fromCircle(center: center, radius: radius));
  }

  @override
  bool shouldReclip(covariant CircleRevealClipper oldClipper) {
    return oldClipper.revealPercent != revealPercent;
  }
}

Explanation:

  • The CircleRevealClipper takes a revealPercent (a double between 0.0 and 1.0) in its constructor.
  • getClip calculates the center of the widget and determines the maximum possible radius needed to cover the entire rectangle (half of the diagonal length) when revealPercent is 1.0. It then scales this maximum radius by revealPercent to get the current radius for the circle.
  • addOval(Rect.fromCircle(...)) creates a circular path.
  • shouldReclip ensures the clipper rebuilds only when revealPercent changes, optimizing performance by avoiding unnecessary re-clipping.

2. Set Up the StatefulWidget for Animation

Next, we'll create a StatefulWidget to manage the AnimationController and Tween, and integrate the CustomClipper with an image.


import 'package:flutter/material.dart';
// Import CircleRevealClipper if it's in a separate file
// import 'circle_reveal_clipper.dart'; 

class ImageRevealAnimation extends StatefulWidget {
  final String imageUrl;

  const ImageRevealAnimation({Key? key, required this.imageUrl}) : super(key: key);

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

class _ImageRevealAnimationState extends State
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 1000), // 1 second animation
    );

    _animation = Tween(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(
        parent: _controller,
        curve: Curves.easeOutCubic, // Customize your curve here
      ),
    );

    // Start the animation when the widget is initialized
    _controller.forward(); 
  }

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

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _animation,
      builder: (context, child) {
        return ClipPath(
          clipper: CircleRevealClipper(_animation.value),
          child: child,
        );
      },
      child: Image.network(
        widget.imageUrl,
        fit: BoxFit.cover,
        width: double.infinity,
        height: double.infinity,
      ),
    );
  }
}

Explanation:

  • SingleTickerProviderStateMixin is used to provide a Ticker for AnimationController, preventing animations from consuming resources when off-screen.
  • _controller is initialized with a duration and vsync: this.
  • _animation is created using a Tween from 0.0 to 1.0, wrapped in a CurvedAnimation for a smoother effect (e.g., Curves.easeOutCubic).
  • _controller.forward() starts the animation when the widget first builds.
  • dispose() is crucial to release resources held by the AnimationController when the widget is removed from the tree.
  • The build method uses AnimatedBuilder. This widget listens to _animation and calls its builder function whenever the animation value changes.
  • Inside the builder, ClipPath uses CircleRevealClipper with the current _animation.value to clip its child.
  • The child of AnimatedBuilder (the Image.network in this case) is created once and passed to the builder function. This is an optimization, as the image itself doesn't rebuild unnecessarily with every animation frame.

3. Integrating the Animation into Your Application

You can now use the ImageRevealAnimation widget anywhere in your Flutter app.


import 'package:flutter/material.dart';
// Ensure ImageRevealAnimation and CircleRevealClipper are imported
// import 'image_reveal_animation.dart'; // Assuming they are in the same file or properly imported

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Image Reveal Demo'),
        ),
        body: Center(
          child: Container( // Using Container for better size control
            width: 300,
            height: 200,
            decoration: BoxDecoration(
              border: Border.all(color: Colors.grey, width: 2),
              borderRadius: BorderRadius.circular(12),
            ),
            child: ClipRRect( // Clip the image reveal within a rounded rectangle
              borderRadius: BorderRadius.circular(10),
              child: ImageRevealAnimation(
                imageUrl: 'https://picsum.photos/id/237/800/600', // Example image URL
              ),
            ),
          ),
        ),
      ),
    );
  }
}

Further Enhancements (Optional)

  • Different Shapes: Modify getClip in your CustomClipper to create square, star, or custom polygon reveals.
  • Reverse Animation: Call _controller.reverse() or add a button/gesture to trigger the reverse reveal, making the image disappear.
  • Conditional Reveal: Trigger the animation based on user interaction (e.g., button press, scroll position, visibility).
  • Overlay Effect: Combine ClipPath with an AnimatedOpacity to fade in the image simultaneously with the reveal.
  • Local Images: Use Image.asset instead of Image.network for local image assets by adjusting the ImageRevealAnimation widget constructor.

Conclusion

The combination of ClipPath, CustomClipper, and Flutter's animation framework provides a powerful toolkit for crafting unique and engaging visual effects. The image reveal animation, as demonstrated, is just one example of how these elements can be orchestrated to enhance the user experience, adding a layer of professionalism and delight to your Flutter applications. By understanding these core concepts, you're well-equipped to explore and implement even more complex and creative animations.

Related Articles

May 14, 2026

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter Countdown timers are essential in many applic

May 11, 2026

Unleashing Dynamic UIs: Flutter's Animation Prowess

Unleashing Dynamic UIs: Flutter's Animation Prowess for Slide & Scale Effects Flutter's declarative UI framework, combined with its powerful animation capabilit

May 11, 2026

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy A well-designed Product Detail Page (PDP) is