Skip to content

iambhabha/instagram_like_animation_button

Repository files navigation

📱 Instagram Like Animation Button

pub package License: MIT Flutter

A fully customizable, OOP-based Flutter package that replicates the Instagram Reels double-tap heart animation — the heart pops up at the tap position, rotates, and flies to a target widget.

Demo


✨ Features

Feature Description
🎯 Fly-to-Target Heart flies from double-tap position to any target widget
🎨 Custom Gradients Use any gradient on the heart icon
🔧 Custom Widget Builder Replace the heart with your own widget (emoji, icon, lottie, etc.)
📳 Haptic Control Enable/disable/change haptic feedback type (light, medium, heavy, none)
Animation Config Customize duration, curves, scale, rotation, timing — 16 parameters
🎭 Full OOP Mixins, extensions, models, services — clean architecture
📦 Zero Dependencies Only depends on Flutter SDK
🔁 DoubleTapDetector Built-in gesture handler with position data

🚀 Getting Started

Installation

Add to your pubspec.yaml:

dependencies:
  instagram_like_animation_button: ^4.0.0

Or with a Git reference:

dependencies:
  instagram_like_animation_button:
    git:
      url: https://github.com/iambhabha/instagram_like_animation_button.git

Then run:

flutter pub get

Import

import 'package:instagram_like_animation_button/instagram_like_animation_button.dart';

📖 Quick Start

class _MyPageState extends State<MyPage> {
  final GlobalKey likeKey = GlobalKey();
  bool isLiked = false;
  TapDownDetails? tapDetails;

  @override
  Widget build(BuildContext context) {
    return DoubleTapDetector(
      onDoubleTap: (details) => setState(() => tapDetails = details),
      child: Stack(
        children: [
          // Your content (image, video, reels, etc.)
          Container(color: Colors.black),

          // Target icon with GlobalKey
          InkWell(
            key: likeKey,
            onTap: () => setState(() => isLiked = !isLiked),
            child: Icon(
              Icons.favorite,
              color: isLiked ? Colors.red : Colors.white,
            ),
          ),

          // Animation layer
          if (tapDetails != null)
            ReelAnimationLike(
              key: ValueKey(tapDetails),
              likeKey: likeKey,
              position: tapDetails!.globalPosition,
              onLikeCall: () => setState(() => isLiked = true),
              onCompleteAnimation: () => setState(() => tapDetails = null),
            ),
        ],
      ),
    );
  }
}

🏗️ Architecture

lib/
├── instagram_like_animation_button.dart    ← Barrel export (single import)
└── src/
    ├── core/
    │   ├── enums.dart                      ← HapticFeedbackType
    │   └── typedefs.dart                   ← AnimationWidgetBuilder, callbacks
    ├── config/
    │   └── like_animation_config.dart      ← 16 configurable animation params
    ├── mixins/
    │   └── haptic_feedback_mixin.dart      ← Haptic feedback mixin
    ├── extensions/
    │   ├── global_key_extensions.dart      ← GlobalKey.centerPosition, etc.
    │   └── offset_extensions.dart          ← Offset.deltaTo()
    ├── models/
    │   └── like_animation_style.dart       ← Gradient, color, icon size
    ├── resources/
    │   ├── asset_res.dart                  ← Asset path constants
    │   ├── color_res.dart                  ← Color constants
    │   └── style_res.dart                  ← Theme gradient
    ├── services/
    │   ├── haptic_manager.dart             ← Singleton haptic service
    │   └── debounce_action.dart            ← Singleton debounce service
    └── widgets/
        ├── reel_animation_like.dart        ← Main animation widget ⭐
        ├── heart_widget.dart               ← Default heart icon
        ├── gradient_icon.dart              ← Gradient ShaderMask wrapper
        └── double_tap_detector.dart        ← Double-tap gesture handler

📚 API Reference

ReelAnimationLike — Main Widget ⭐

The core widget. Place in a Stack — it animates from positionlikeKey target.

Parameter Type Required Description
likeKey GlobalKey Target widget key
position Offset Tap position (global)
config LikeAnimationConfig Animation parameters
style LikeAnimationStyle Visual style
builder AnimationWidgetBuilder? Custom widget builder
onLikeCall VoidCallback? Called on like
onCompleteAnimation VoidCallback? Called on animation end
leftRightPosition double X offset adjustment
topBottomPosition double Y offset adjustment
ReelAnimationLike(
  likeKey: targetKey,
  position: tapPosition,
  config: const LikeAnimationConfig(
    duration: Duration(milliseconds: 800),
    hapticType: HapticFeedbackType.medium,
    scaleMax: 2.0,
  ),
  style: const LikeAnimationStyle(
    gradient: LinearGradient(colors: [Colors.pink, Colors.orange]),
    iconSize: Size(60, 60),
  ),
  onLikeCall: () => print('Liked!'),
)

Important

🎯 Position Fine-Tuning — leftRightPosition & topBottomPosition

If the animation's starting position doesn't look accurate (heart appears slightly off from the tap point), you can adjust it using these two parameters:

Parameter What It Does When to Use
leftRightPosition Shifts the animation horizontally (X-axis) Heart appears too far left/right from tap
topBottomPosition Shifts the animation vertically (Y-axis) Heart appears too high/low from tap

Why is this needed? Every app has different layouts — AppBar, BottomNavigationBar, SafeArea, padding, etc. These affect where the tap coordinates land vs where the animation renders in the Stack. These offsets let you compensate for that difference.

How to use:

ReelAnimationLike(
  likeKey: likeKey,
  position: tapDetails!.globalPosition,

  // 👇 Adjust these values until the heart appears exactly at the tap point
  leftRightPosition: 8,    // shift 8px to the left
  topBottomPosition: 65,   // shift 65px up (e.g., AppBar height)

  onLikeCall: () {},
)

Quick guide:

  • topBottomPosition: 56 → standard AppBar height
  • topBottomPosition: 80 → AppBar + status bar
  • leftRightPosition: 0 → no horizontal adjustment needed (most cases)

💡 Tip: Start with 0 for both, double-tap on screen, and see where the heart appears. Then adjust the values until it lands exactly on your finger tap position.


LikeAnimationConfig — Animation Parameters

Immutable config with copyWith() support. 16 customizable properties:

Property Type Default Description
duration Duration 600ms Total animation duration
scaleBegin double 0.0 Starting scale
scaleMax double 1.5 Peak scale (pop size)
scaleEnd double 0.7 Final scale
scaleCurveUp Curve easeOut Scale-up curve
scaleCurveDown Curve bounceIn Scale-down curve
scaleUpWeight double 30 Scale-up timing weight
scaleDownWeight double 100 Scale-down timing weight
initialOffset Offset (0, -50) Offset from tap
holdWeight double 50 Hold phase weight
flyWeight double 50 Fly phase weight
flyCurve Curve easeInOut Flight curve
rotationRange double 0.5 Max rotation (radians)
hapticType HapticFeedbackType light Haptic feedback type
fadeDuration Duration 500ms Fade-out duration
completionDelay Duration 500ms Delay before onComplete
// Disable haptic, fast animation
const config = LikeAnimationConfig(
  duration: Duration(milliseconds: 400),
  hapticType: HapticFeedbackType.none,
  scaleMax: 2.0,
);

// Modify existing config
final modified = config.copyWith(hapticType: HapticFeedbackType.heavy);

LikeAnimationStyle — Visual Style

Property Type Default Description
gradient Gradient? theme gradient Gradient overlay
iconColor Color? #FF2D55 Icon tint color
iconSize Size 80×80 Icon dimensions
const style = LikeAnimationStyle(
  gradient: LinearGradient(colors: [Colors.purple, Colors.blue]),
  iconColor: Colors.pink,
  iconSize: Size(50, 50),
);

DoubleTapDetector — Gesture Handler

Detects double-taps with position data (Flutter's onDoubleTap doesn't provide this).

DoubleTapDetector(
  onDoubleTap: (details) => print(details.globalPosition),
  onTap: () => print('Single tap'),
  behavior: HitTestBehavior.opaque,
  child: YourContent(),
)

HapticFeedbackType — Enum

Value Description
none ❌ No haptic
light 💫 Light tap (default)
medium 📳 Medium tap
heavy 💥 Heavy tap
selection 🔘 Selection click
vibrate 📱 Standard vibration

HeartWidget — Default Icon

HeartWidget(
  style: LikeAnimationStyle(iconColor: Colors.pink, iconSize: Size(60, 60)),
)

GradientIcon — Gradient Wrapper

GradientIcon(
  gradient: LinearGradient(colors: [Colors.red, Colors.orange]),
  child: Icon(Icons.favorite, size: 48),
)

Extensions

// GlobalKeyX — on GlobalKey
final Offset? pos    = myKey.globalPosition;    // top-left
final Offset? center = myKey.centerPosition;    // center
final Size?   size   = myKey.widgetSize;         // size

// OffsetX — on Offset
final delta = tapPosition.deltaTo(targetPosition);

Services

// HapticManager (singleton)
HapticManager.instance.trigger(HapticFeedbackType.light);
HapticManager.instance.light();
HapticManager.instance.medium();

// DebounceAction (singleton)
DebounceAction.instance.call(() => print('Done'), milliseconds: 500);
DebounceAction.instance.cancel();

Resources

AssetRes.icFillHeart    // 'assets/ic_fill_heart.png'
AssetRes.icHeart        // 'assets/ic_heart.png'

ColorRes.likeRed        // #FF2D55
ColorRes.whitePure      // #FFFFFF
ColorRes.blackPure      // #000000

StyleRes.themeGradient   // Default LinearGradient

🧩 Custom Widget Builder

Replace the default heart with any widget — emoji, Lottie, custom icon:

ReelAnimationLike(
  likeKey: likeKey,
  position: tapPosition,
  builder: (context, scale, rotation, opacity) {
    return Opacity(
      opacity: opacity,
      child: Transform.scale(
        scale: scale.value,
        child: Transform.rotate(
          angle: rotation.value,
          child: Text('🔥', style: TextStyle(fontSize: 60)),
        ),
      ),
    );
  },
  onLikeCall: () {},
)

🎮 Full Example

See the complete working example in example/lib/main.dart.

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

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

  @override
  State<ReelPage> createState() => _ReelPageState();
}

class _ReelPageState extends State<ReelPage> {
  final GlobalKey likeKey = GlobalKey();
  bool isLiked = false;
  TapDownDetails? tapDetails;

  @override
  Widget build(BuildContext context) {
    return DoubleTapDetector(
      onDoubleTap: (details) => setState(() => tapDetails = details),
      child: Stack(
        children: [
          Container(color: Colors.black), // Your video/image

          // Right-side icons (like Instagram Reels)
          Positioned(
            right: 16,
            bottom: 100,
            child: InkWell(
              key: likeKey,
              onTap: () => setState(() => isLiked = !isLiked),
              child: Image.asset(
                isLiked ? AssetRes.icFillHeart : AssetRes.icHeart,
                width: 28, height: 28,
                color: isLiked ? ColorRes.likeRed : ColorRes.whitePure,
                package: 'instagram_like_animation_button',
              ),
            ),
          ),

          // Animation
          if (tapDetails != null)
            ReelAnimationLike(
              key: ValueKey(tapDetails),
              likeKey: likeKey,
              position: tapDetails!.globalPosition,
              config: const LikeAnimationConfig(
                hapticType: HapticFeedbackType.light,
              ),
              style: const LikeAnimationStyle(iconSize: Size(50, 50)),
              onLikeCall: () {
                if (!isLiked) setState(() => isLiked = true);
              },
              onCompleteAnimation: () => setState(() => tapDetails = null),
            ),
        ],
      ),
    );
  }
}

📋 Requirements

Version
Flutter ≥1.17.0
Dart ^3.10.7

📄 License

MIT License — see LICENSE for details.

🤝 Contributing

Contributions are welcome! Please open an issue or submit a pull request.


Made with ❤️ by @iambhabha

About

Instagram Reels-style double tap heart animation for Flutter. Fully customizable fly-to-target like animation with gradients, haptic control, 16 animation parameters, and clean OOP architecture. Zero external dependencies.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors