61 lines
1.7 KiB
Dart
61 lines
1.7 KiB
Dart
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<TapBounce> createState() => _TapBounceState();
|
|
}
|
|
|
|
class _TapBounceState extends State<TapBounce>
|
|
with SingleTickerProviderStateMixin {
|
|
late final AnimationController _controller = AnimationController(
|
|
vsync: this,
|
|
duration: widget.duration,
|
|
);
|
|
late final Animation<double> _scale = Tween<double>(
|
|
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,
|
|
),
|
|
);
|
|
}
|
|
}
|