Flutter 文字中划线动画StrikeThroughTextAnimation
概述
接上文 CheckBoxAnimation 动画,在加上文字的动画,刚好可以做一个组合的列表动画。文字部分动画主要就是左右移动、颜色变化以及在绘制文字中划线。
效果预览
基本使用
StrikeThroughText(
text: "1. Task Item StrikeThroughText",
textStyle: const TextStyle(
fontSize: 18,
),
inactiveTextColor: Colors.red,
textColor: Colors.blue,
strikethrough: isCheck,
onChange: (value) {
setState(() {
isCheck = value;
});
},
)
实现
1、布局
首先完成 widget 的布局和样式,这里采用了 Stack 布局,首先添加文字和文字样式,在文字的中间放置一个横线作为中划线。 大致布局如下:
Stack(
children: [
Text(
"Task Item",
maxLines: 1,
softWrap: false,
style: TextStyle(
fontSize: 18,
),
),
Positioned(
top: 0,
bottom: 0,
left: 0,
right: 0,
child: CustomPaint(
painter: StrikeThroughTextPainter(
...,
),
),
),
],
);
2、绘制中划线
绘制中划线,首先需要知道要绘制多长。这里可以使用 TextPainter
来测绘文字的宽高,这里写成一个通用的方法,传入 Text 的text和textStyle,返回文字的宽高:
class TextSizeBox {
final double width;
final double height;
TextSizeBox({required this.width, required this.height});
factory TextSizeBox.fromText(String text, {TextStyle? textStyle}) {
final TextPainter textPainter = TextPainter(
text: TextSpan(text: text, style: textStyle),
maxLines: 1,
textDirection: TextDirection.ltr,
)..layout(minWidth: 0, maxWidth: double.infinity);
return TextSizeBox(width: textPainter.width, height: textPainter.height);
}
}
知道了文字的宽就等于知道绘制文字的中划线宽度了。
StrikeThroughTextPainter(
width: TextSizeBox.fromText(widget.text, textStyle: widget.textStyle).width,
height: 2.0,
color: Colors.grey,
)
class StrikeThroughTextPainter extends CustomPainter {
final double width;
final double height;
final Color color;
StrikeThroughTextPainter(
{required this.width, required this.height, required this.color});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = color
..strokeWidth = height
..strokeCap = StrokeCap.round;
if (width > 0) {
canvas.drawLine(
Offset(0, size.height / 2),
Offset(width > size.width ? size.width : width, size.height / 2),
paint);
}
}
@override
bool shouldRepaint(StrikeThroughTextPainter oldDelegate) {
return width != oldDelegate.width || height != oldDelegate.height;
}
}
3、动画
首先是左右移动动画,先创建一个 AnimationController
,在创建一个Tween<Offset>来控制左右移动的偏移量
_offsetController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 100),
);
_offsetAnimation = Tween<Offset>(
begin: const Offset(0.0, 0.0),
end: const Offset(0.2, 0.0),
).animate(CurvedAnimation(
parent: _offsetController,
curve: Curves.easeInOut,
));
使用 SlideTransition
来控制左右平移偏移量
SlideTransition(
position: _offsetAnimation,
child: Stack(
......
),
)
因为颜色变化和划中划线是同步进行的,所以只需要创建一个AnimationController
来控制颜色和进度的动画
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 300),
value: 1,
);
_animation = Tween(begin: 0.0, end: 1.0).animate(_animationController);
_animationColor = ColorTween(
begin: Colors.black87,
end: Colors.grey)
.animate(_animationController);
接下来就是在需要动画的 widget 上放上动画就可以了.
完整代码
import 'package:flutter/material.dart';
class StrikeThroughText extends StatefulWidget {
final String text;
final TextStyle textStyle;
final bool strikethrough;
final Color? textColor;
final Color? inactiveTextColor;
final ValueChanged? onChange;
const StrikeThroughText({
Key? key,
required this.text,
required this.textStyle,
this.strikethrough = false,
this.textColor,
this.inactiveTextColor,
this.onChange,
}) : super(key: key);
@override
StrikeThroughTextState createState() => StrikeThroughTextState();
}
class StrikeThroughTextState extends State<StrikeThroughText>
with TickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _animation;
late Animation _animationColor;
late AnimationController _offsetController;
late Animation<Offset> _offsetAnimation;
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 300),
value: widget.strikethrough ? 1 : 0,
);
_animation = Tween(begin: 0.0, end: 1.0).animate(_animationController);
_animationColor = ColorTween(
begin: widget.textColor ?? Colors.black87,
end: widget.inactiveTextColor ?? Colors.grey)
.animate(_animationController);
_offsetController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 100),
);
_offsetAnimation = Tween<Offset>(
begin: const Offset(0.0, 0.0),
end: const Offset(0.2, 0.0),
).animate(CurvedAnimation(
parent: _offsetController,
curve: Curves.easeInOut,
));
}
@override
void didUpdateWidget(covariant StrikeThroughText oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.strikethrough != widget.strikethrough) {
if (widget.strikethrough) {
startAnimation();
} else {
reset();
}
}
}
@override
void dispose() {
_animationController.dispose();
_offsetController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
if (widget.strikethrough) {
widget.onChange?.call(false);
} else {
widget.onChange?.call(true);
}
},
child: SlideTransition(
position: _offsetAnimation,
child: Stack(
children: [
AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Text(
widget.text,
maxLines: 1,
softWrap: false,
style: widget.textStyle.copyWith(
color: _animationColor.value,
overflow: TextOverflow.clip,
),
);
}),
// AnimatedDefaultTextStyle(
// style: widget.textStyle..copyWith(color: _animationColor.value),
// duration: const Duration(milliseconds: 500),
// child: Text(widget.text),
// ),
AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Positioned(
left: 0,
right: 0,
top: 0,
bottom: 0,
child: CustomPaint(
painter: StrikeThroughTextPainter(
width: TextSizeBox.fromText(widget.text,
textStyle: widget.textStyle)
.width *
_animation.value,
height: 2.0,
color: widget.inactiveTextColor ?? Colors.grey,
),
),
);
},
),
],
),
),
);
}
void startAnimation() async {
_animationController.reset();
await _offsetController.forward();
await _offsetController.reverse();
_animationController.forward();
}
void reset() {
_animationController.reset();
}
}
class StrikeThroughTextPainter extends CustomPainter {
final double width;
final double height;
final Color color;
StrikeThroughTextPainter(
{required this.width, required this.height, required this.color});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = color
..strokeWidth = height
..strokeCap = StrokeCap.round;
if (width > 0) {
canvas.drawLine(
Offset(0, size.height / 2),
Offset(width > size.width ? size.width : width, size.height / 2),
paint);
}
}
@override
bool shouldRepaint(StrikeThroughTextPainter oldDelegate) {
return width != oldDelegate.width || height != oldDelegate.height;
}
}
class TextSizeBox {
final double width;
final double height;
TextSizeBox({required this.width, required this.height});
factory TextSizeBox.fromText(String text, {TextStyle? textStyle}) {
final TextPainter textPainter = TextPainter(
text: TextSpan(text: text, style: textStyle),
maxLines: 1,
textDirection: TextDirection.ltr,
)..layout(minWidth: 0, maxWidth: double.infinity);
return TextSizeBox(width: textPainter.width, height: textPainter.height);
}
}
以上就是Flutter 文字中划线动画StrikeThroughTextAnimation的详细内容,更多关于Flutter 文字中划线的资料请关注编程网其它相关文章!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341