Free copy-paste Button 010 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_010.dart) and use the widget anywhere in your widget tree.
import 'package:flutter/material.dart';
class Button010 extends StatefulWidget {
const Button010({
super.key,
this.label = 'Hover me',
this.onTap,
this.lineColor = const Color(0xFFF44336),
this.textColor = Colors.white,
this.fontSize = 25.0,
this.duration = const Duration(milliseconds: 1000),
});
final String label;
final VoidCallback? onTap;
final Color lineColor, textColor;
final double fontSize;
final Duration duration;
@override
State<Button010> createState() => _Button010State();
}
class _Button010State extends State<Button010>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
late final Animation<double> _anim;
bool _pressed = false;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: widget.duration);
_anim = CurvedAnimation(parent: _controller, curve: Curves.easeInOutCubic);
}
@override
void didUpdateWidget(covariant Button010 oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.duration != widget.duration) {
_controller.duration = widget.duration;
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _onHover(bool hover) {
if (hover) {
_controller.forward();
} else {
_controller.reverse();
}
}
@override
Widget build(BuildContext context) {
return MouseRegion(
opaque: true,
cursor: SystemMouseCursors.click,
onEnter: (_) => _onHover(true),
onExit: (_) => _onHover(false),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTapDown: (_) => setState(() => _pressed = true),
onTapUp: (_) {
setState(() => _pressed = false);
widget.onTap?.call();
},
onTapCancel: () => setState(() => _pressed = false),
child: AnimatedScale(
scale: _pressed ? 0.96 : 1.0,
duration: const Duration(milliseconds: 100),
child: AnimatedBuilder(
animation: _anim,
builder: (context, child) {
return CustomPaint(
foregroundPainter: _UnderlineHoverPainter(
progress: _anim.value,
lineColor: widget.lineColor,
),
child: child,
);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 6),
child: Text(
widget.label,
style: TextStyle(
color: widget.textColor,
fontSize: widget.fontSize,
fontWeight: FontWeight.w600,
letterSpacing: 0.5,
),
),
),
),
),
),
);
}
}
class _UnderlineHoverPainter extends CustomPainter {
const _UnderlineHoverPainter({
this.progress = 0.0,
this.lineColor = const Color(0xFFF44336),
});
final double progress;
final Color lineColor;
@override
void paint(Canvas canvas, Size size) {
if (progress <= 0.0) return;
final Paint paint = Paint()
..color = lineColor
..style = PaintingStyle.fill;
const double lineHeight = 2.0;
final double lineWidth = size.width * progress;
canvas.drawRect(
Rect.fromLTWH(size.width - lineWidth, 0, lineWidth, lineHeight),
paint,
);
canvas.drawRect(
Rect.fromLTWH(0, size.height - lineHeight, lineWidth, lineHeight),
paint,
);
}
@override
bool shouldRepaint(_UnderlineHoverPainter oldDelegate) {
return oldDelegate.progress != progress ||
oldDelegate.lineColor != lineColor;
}
}