import 'package:flutter/material.dart'; /// Envolve [child] com um efeito de "aperto" ao toque: encolhe levemente /// no pointer-down e volta ao tamanho normal com uma pequena mola ao soltar. /// /// Usa [Listener] (eventos de ponteiro puros) em vez de [GestureDetector] /// para não competir na arena de gestos com um `InkWell`/`Button` filho — /// o toque real continua a ser tratado pelo widget interno normalmente. class TapBounce extends StatefulWidget { const TapBounce({ super.key, required this.child, this.scale = 0.94, this.duration = const Duration(milliseconds: 110), }); final Widget child; final double scale; final Duration duration; @override State createState() => _TapBounceState(); } class _TapBounceState extends State with SingleTickerProviderStateMixin { late final AnimationController _controller = AnimationController( vsync: this, duration: widget.duration, ); late final Animation _scale = Tween( begin: 1.0, end: widget.scale, ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut)); @override void dispose() { _controller.dispose(); super.dispose(); } void _press(PointerDownEvent _) => _controller.forward(); void _release([PointerEvent? _]) => _controller.reverse(); @override Widget build(BuildContext context) { return Listener( onPointerDown: _press, onPointerUp: _release, onPointerCancel: _release, child: AnimatedBuilder( animation: _scale, builder: (context, child) => Transform.scale(scale: _scale.value, child: child), child: widget.child, ), ); } }