import 'package:flutter/material.dart'; import 'package:haptic_feedback/haptic_feedback.dart'; class BouncyTap extends StatefulWidget { final Widget child; final VoidCallback? onPressed; final double scaleDown; final Duration duration; final bool enableHaptic; final HitTestBehavior behavior; const BouncyTap({ super.key, required this.child, this.onPressed, this.scaleDown = 0.95, this.duration = const Duration(milliseconds: 100), this.enableHaptic = true, this.behavior = HitTestBehavior.opaque, }); @override State createState() => _BouncyTapState(); } class _BouncyTapState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation _scaleAnimation; @override void initState() { super.initState(); _controller = AnimationController(vsync: this, duration: widget.duration); _scaleAnimation = Tween(begin: 1.0, end: widget.scaleDown).animate( CurvedAnimation(parent: _controller, curve: Curves.easeInOut), ); } @override void dispose() { _controller.dispose(); super.dispose(); } void _onTapDown(TapDownDetails details) { if (widget.onPressed != null) { if (widget.enableHaptic) { Haptics.vibrate(HapticsType.light); } _controller.forward(); } } void _onTapUp(TapUpDetails details) { if (widget.onPressed != null) { _controller.reverse(); widget.onPressed!(); } } void _onTapCancel() { if (widget.onPressed != null) { _controller.reverse(); } } @override Widget build(BuildContext context) { return GestureDetector( behavior: widget.behavior, onTapDown: _onTapDown, onTapUp: _onTapUp, onTapCancel: _onTapCancel, child: AnimatedBuilder( animation: _scaleAnimation, builder: (context, child) { return Transform.scale( scale: _scaleAnimation.value, alignment: Alignment.center, child: widget.child, ); }, ), ); } }