Free copy-paste Button 007 for Flutter: a ready-to-use button widget with full Dart source code and a live preview. Works on desktop. No external packages.
Copy the Dart code below into a new file in your Flutter project (for example lib/widgets/button_007.dart) and use the widget anywhere in your widget tree.
import 'package:flutter/material.dart';
class Button007 extends StatefulWidget {
const Button007({
super.key,
this.label = 'Hover me',
this.onTap,
this.activeColor = const Color(0xFF66FF66),
this.baseColor = Colors.black,
this.initialTextColor = const Color(0xFFF8F8FF),
this.activeTextColor = Colors.black,
this.fontSize = 17.0,
this.padding = const EdgeInsets.symmetric(horizontal: 34, vertical: 15),
this.duration = const Duration(milliseconds: 1000),
});
final String label;
final VoidCallback? onTap;
final Color activeColor;
final Color baseColor;
final Color initialTextColor;
final Color activeTextColor;
final double fontSize;
final EdgeInsetsGeometry padding;
final Duration duration;
@override
State<Button007> createState() => _Button007State();
}
class _Button007State extends State<Button007>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
late final Animation<double> _animation;
bool _isPressed = false;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: widget.duration);
_animation = CurvedAnimation(
parent: _controller,
curve: const Cubic(0.3, 1.0, 0.8, 1.0),
reverseCurve: const Cubic(0.3, 1.0, 0.8, 1.0),
);
}
@override
void didUpdateWidget(covariant Button007 oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.duration != oldWidget.duration) {
_controller.duration = widget.duration;
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _handleHover(bool isHovered) {
if (isHovered) {
_controller.forward();
} else {
_controller.reverse();
}
}
@override
Widget build(BuildContext context) {
return MouseRegion(
cursor: SystemMouseCursors.click,
onEnter: (_) => _handleHover(true),
onExit: (_) => _handleHover(false),
child: GestureDetector(
onTapDown: (_) => setState(() => _isPressed = true),
onTapUp: (_) {
setState(() => _isPressed = false);
widget.onTap?.call();
},
onTapCancel: () => setState(() => _isPressed = false),
child: AnimatedScale(
scale: _isPressed ? 0.96 : 1.0,
duration: const Duration(milliseconds: 100),
curve: Curves.easeOutCubic,
child: ClipRRect(
borderRadius: BorderRadius.circular(500),
child: AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return CustomPaint(
painter: _SkewRevealPainter(
progress: _animation.value,
activeColor: widget.activeColor,
baseColor: widget.baseColor,
),
child: Padding(
padding: widget.padding,
child: Text(
widget.label,
style: TextStyle(
color: Color.lerp(
widget.initialTextColor,
widget.activeTextColor,
_animation.value,
),
fontSize: widget.fontSize,
fontWeight: FontWeight.w700,
letterSpacing: 0.85,
),
),
),
);
},
),
),
),
),
);
}
}
class _SkewRevealPainter extends CustomPainter {
const _SkewRevealPainter({
this.progress = 0.0,
this.activeColor = const Color(0xFF66FF66),
this.baseColor = Colors.black,
});
final double progress;
final Color activeColor;
final Color baseColor;
@override
void paint(Canvas canvas, Size size) {
final Rect fullRect = Offset.zero & size;
canvas.drawRect(fullRect, Paint()..color = activeColor);
final double slideOffset = size.width * 1.35 * progress;
final double skewAmount = size.height * 0.65;
final Path path = Path()
..moveTo(-size.width * 0.1 + slideOffset, 0)
..lineTo(size.width * 1.2 + slideOffset, 0)
..lineTo(size.width * 1.2 - skewAmount + slideOffset, size.height)
..lineTo(-size.width * 0.1 - skewAmount + slideOffset, size.height)
..close();
canvas.drawPath(path, Paint()..color = baseColor);
}
@override
bool shouldRepaint(_SkewRevealPainter oldDelegate) {
return oldDelegate.progress != progress ||
oldDelegate.activeColor != activeColor ||
oldDelegate.baseColor != baseColor;
}
}