I'm trying to create a text with an animation on hover, including a dot in front of it, using Flutter. The design comes from this website (look at the bottom section you'll find what I'm reproducing).
Here's the code for my component:
import 'package:flutter/material.dart';
class MyText extends StatefulWidget {
final String text;
final TextStyle textStyle;
final double width;
final double underlineOffset;
final bool hasDot;
const MyText({
super.key,
required this.text,
required this.textStyle,
required this.width,
required this.underlineOffset,
this.hasDot = false,
});
@override
State<MyText> createState() => _MyTextState();
}
class _MyTextState extends State<MyText> with TickerProviderStateMixin {
late AnimationController _colorAnimationController;
late Animation _colorAnimation;
late AnimationController _underlineAnimationController;
late Animation _underlineAnimation;
@override
void initState() {
_colorAnimationController = AnimationController(
duration: const Duration(milliseconds: 250),
vsync: this,
)..addListener(() {
setState(() {});
});
_colorAnimation = ColorTween(
begin: Colors.black,
end: Colors.orange,
).animate(_colorAnimationController);
_underlineAnimationController = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
)..addListener(() {
if (_underlineAnimationController.isCompleted &&
_underlineAnimationController.value == 1) {
_underlineAnimationController.reset();
}
setState(() {});
});
_underlineAnimation = Tween(begin: -widget.width, end: widget.width)
.animate(_underlineAnimationController);
super.initState();
}
@override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (event) {
_colorAnimationController.forward();
_underlineAnimationController.animateTo(0.5);
},
onExit: (event) {
_colorAnimationController.reverse();
_underlineAnimationController.animateTo(1);
},
cursor: SystemMouseCursors.click,
child: ClipRRect(
child: Container(
alignment: Alignment.centerRight,
width: widget.hasDot ? widget.width + 25 : widget.width,
height: widget.underlineOffset + 2,
// color: Colors.green,
child: Stack(
clipBehavior: Clip.none,
children: [
// Points (si il y en a un)
if (widget.hasDot)
const Positioned(
top: 9,
left: -26,
child: Icon(
Icons.circle,
color: Colors.black,
size: 10,
),
),
// Texte
Stack(
children: [
Text(
widget.text,
style: widget.textStyle.copyWith(
color: _colorAnimation.value,
),
),
],
),
// Underline (onHover animation)
Positioned(
top: widget.underlineOffset,
left: _underlineAnimation.value,
child: Container(
width: widget.width,
height: 1,
color: Colors.orange,
),
)
],
),
),
),
);
}
}
It's all working fine, but when I set hasDot: true
, the orange line appears under the dot.
Note: I want the underline animation to occur only under the text and not under the dot.
My issue is how to achieve the same animation effect as when hasDot
is false
, but with the dot included and the orange line hidden under the dot.
Additional info: If you need the full code of the Flutter project, please check my GitHub repo.
Thank you in advance for your help.
Have a great time coding!
Update: Before posting this question, I tried many ways to accomplish this without any success. After posting this question, I found the solution in about 2 seconds...
So here is the full code of the component with the little modifications (basically added a Row to separate the dot from all the rest):
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:kanaknaturals_cursor/utilities/my_active_provider.dart';
import 'package:provider/provider.dart';
class MyText extends StatefulWidget {
final int itemID;
final String text;
final TextStyle textStyle;
final double width;
final double underlineOffset;
final bool hasDot;
const MyText({
Key key,
required this.itemID,
required this.text,
required this.textStyle,
required this.width,
required this.underlineOffset,
this.hasDot = false,
});
@override
State<MyText> createState() => _MyTextState();
}
class _MyTextState extends State<MyText> with TickerProviderStateMixin {
late AnimationController _colorAnimationController;
late Animation _colorAnimation;
late AnimationController _underlineAnimationController;
late Animation _underlineAnimation;
@override
void initState() {
_colorAnimationController = AnimationController(
duration: const Duration(milliseconds: 250),
vsync: this,
)..addListener(() {
setState(() {});
});
_colorAnimation = ColorTween(
begin: Colors.black,
end: Colors.orange,
).animate(_colorAnimationController);
_underlineAnimationController = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
)..addListener(() {
if (_underlineAnimationController.isCompleted &&
_underlineAnimationController.value == 1) {
_underlineAnimationController.reset();
}
setState(() {});
});
_underlineAnimation = Tween(begin: -widget.width, end: widget.width)
.animate(_underlineAnimationController);
super.initState();
}
@override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (event) {
_colorAnimationController.forward();
_underlineAnimationController.animateTo(0.5);
Provider.of<MyActiveProvider>(context, listen: false)
.setActiveItem(widget.itemID);
},
onExit: (event) {
_colorAnimationController.reverse();
_underlineAnimationController.animateTo(1);
Provider.of<MyActiveProvider>(context, listen: false)
.setActiveItem(null);
},
cursor: SystemMouseCursors.click,
child: Row(
children: [
// Points (if there is one)
if (widget.hasDot)
const Row(
children: [
Positioned(
top: 9,
left: -26,
child: Icon(
Icons.circle,
color: Colors.black,
size: 10,
),
),
Gap(10),
],
),
ClipRRect(
child: Container(
alignment: Alignment.centerRight,
width: widget.hasDot ? widget.width : widget.width,
height: widget.underlineOffset + 2,
// color: Colors.green,
child: Stack(
clipBehavior: Clip.none,
children: [
// Text
Stack(
children: [
Text(
widget.text,
style: widget.textStyle.copyWith(
color: _colorAnimation.value,
),
),
],
),
// Underline (onHover animation)
Positioned(
top: widget.underlineOffset,
left: _underlineAnimation.value,
child: Container(
width: widget.width,
height: 1,
color: Colors.orange,
),
)
],
),
),
),
],
),
);
}
}