怎么使用Flutter StrikeThroughTextAnimation实现文字中划线动画

其他教程   发布日期:2024年11月30日   浏览次数:219

这篇文章主要介绍“怎么使用Flutter StrikeThroughTextAnimation实现文字中划线动画”,在日常操作中,相信很多人在怎么使用Flutter StrikeThroughTextAnimation实现文字中划线动画问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”怎么使用Flutter StrikeThroughTextAnimation实现文字中划线动画”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

    基本使用

    1. StrikeThroughText(
    2. text: "1. Task Item StrikeThroughText",
    3. textStyle: const TextStyle(
    4. fontSize: 18,
    5. ),
    6. inactiveTextColor: Colors.red,
    7. textColor: Colors.blue,
    8. strikethrough: isCheck,
    9. onChange: (value) {
    10. setState(() {
    11. isCheck = value;
    12. });
    13. },
    14. )

    实现

    1、布局

    首先完成 widget 的布局和样式,这里采用了 Stack 布局,首先添加文字和文字样式,在文字的中间放置一个横线作为中划线。 大致布局如下:

    1. Stack(
    2. children: [
    3. Text(
    4. "Task Item",
    5. maxLines: 1,
    6. softWrap: false,
    7. style: TextStyle(
    8. fontSize: 18,
    9. ),
    10. ),
    11. Positioned(
    12. top: 0,
    13. bottom: 0,
    14. left: 0,
    15. right: 0,
    16. child: CustomPaint(
    17. painter: StrikeThroughTextPainter(
    18. ...,
    19. ),
    20. ),
    21. ),
    22. ],
    23. );

    2、绘制中划线

    绘制中划线,首先需要知道要绘制多长。这里可以使用

    1. TextPainter
    来测绘文字的宽高,这里写成一个通用的方法,传入 Text 的text和textStyle,返回文字的宽高:
    1. class TextSizeBox {
    2. final double width;
    3. final double height;
    4. TextSizeBox({required this.width, required this.height});
    5. factory TextSizeBox.fromText(String text, {TextStyle? textStyle}) {
    6. final TextPainter textPainter = TextPainter(
    7. text: TextSpan(text: text, style: textStyle),
    8. maxLines: 1,
    9. textDirection: TextDirection.ltr,
    10. )..layout(minWidth: 0, maxWidth: double.infinity);
    11. return TextSizeBox(width: textPainter.width, height: textPainter.height);
    12. }
    13. }

    知道了文字的宽就等于知道绘制文字的中划线宽度了。

    1. StrikeThroughTextPainter(
    2. width: TextSizeBox.fromText(widget.text, textStyle: widget.textStyle).width,
    3. height: 2.0,
    4. color: Colors.grey,
    5. )
    1. class StrikeThroughTextPainter extends CustomPainter {
    2. final double width;
    3. final double height;
    4. final Color color;
    5. StrikeThroughTextPainter(
    6. {required this.width, required this.height, required this.color});
    7. @override
    8. void paint(Canvas canvas, Size size) {
    9. final paint = Paint()
    10. ..color = color
    11. ..strokeWidth = height
    12. ..strokeCap = StrokeCap.round;
    13. if (width > 0) {
    14. canvas.drawLine(
    15. Offset(0, size.height / 2),
    16. Offset(width > size.width ? size.width : width, size.height / 2),
    17. paint);
    18. }
    19. }
    20. @override
    21. bool shouldRepaint(StrikeThroughTextPainter oldDelegate) {
    22. return width != oldDelegate.width || height != oldDelegate.height;
    23. }
    24. }

    3、动画

    首先是左右移动动画,先创建一个

    1. AnimationController
    ,在创建一个Tween<Offset>来控制左右移动的偏移量
    1. _offsetController = AnimationController(
    2. vsync: this,
    3. duration: const Duration(milliseconds: 100),
    4. );
    5. _offsetAnimation = Tween<Offset>(
    6. begin: const Offset(0.0, 0.0),
    7. end: const Offset(0.2, 0.0),
    8. ).animate(CurvedAnimation(
    9. parent: _offsetController,
    10. curve: Curves.easeInOut,
    11. ));

    使用

    1. SlideTransition
    来控制左右平移偏移量
    1. SlideTransition(
    2. position: _offsetAnimation,
    3. child: Stack(
    4. ......
    5. ),
    6. )

    因为颜色变化和划中划线是同步进行的,所以只需要创建一个

    1. AnimationController
    来控制颜色和进度的动画
    1. _animationController = AnimationController(
    2. vsync: this,
    3. duration: const Duration(milliseconds: 300),
    4. value: 1,
    5. );
    6. _animation = Tween(begin: 0.0, end: 1.0).animate(_animationController);
    7. _animationColor = ColorTween(
    8. begin: Colors.black87,
    9. end: Colors.grey)
    10. .animate(_animationController);

    接下来就是在需要动画的 widget 上放上动画就可以了.

    完整代码

    1. import 'package:flutter/material.dart';
    2. class StrikeThroughText extends StatefulWidget {
    3. final String text;
    4. final TextStyle textStyle;
    5. final bool strikethrough;
    6. final Color? textColor;
    7. final Color? inactiveTextColor;
    8. final ValueChanged? onChange;
    9. const StrikeThroughText({
    10. Key? key,
    11. required this.text,
    12. required this.textStyle,
    13. this.strikethrough = false,
    14. this.textColor,
    15. this.inactiveTextColor,
    16. this.onChange,
    17. }) : super(key: key);
    18. @override
    19. StrikeThroughTextState createState() => StrikeThroughTextState();
    20. }
    21. class StrikeThroughTextState extends State<StrikeThroughText>
    22. with TickerProviderStateMixin {
    23. late AnimationController _animationController;
    24. late Animation<double> _animation;
    25. late Animation _animationColor;
    26. late AnimationController _offsetController;
    27. late Animation<Offset> _offsetAnimation;
    28. @override
    29. void initState() {
    30. super.initState();
    31. _animationController = AnimationController(
    32. vsync: this,
    33. duration: const Duration(milliseconds: 300),
    34. value: widget.strikethrough ? 1 : 0,
    35. );
    36. _animation = Tween(begin: 0.0, end: 1.0).animate(_animationController);
    37. _animationColor = ColorTween(
    38. begin: widget.textColor ?? Colors.black87,
    39. end: widget.inactiveTextColor ?? Colors.grey)
    40. .animate(_animationController);
    41. _offsetController = AnimationController(
    42. vsync: this,
    43. duration: const Duration(milliseconds: 100),
    44. );
    45. _offsetAnimation = Tween<Offset>(
    46. begin: const Offset(0.0, 0.0),
    47. end: const Offset(0.2, 0.0),
    48. ).animate(CurvedAnimation(
    49. parent: _offsetController,
    50. curve: Curves.easeInOut,
    51. ));
    52. }
    53. @override
    54. void didUpdateWidget(covariant StrikeThroughText oldWidget) {
    55. super.didUpdateWidget(oldWidget);
    56. if (oldWidget.strikethrough != widget.strikethrough) {
    57. if (widget.strikethrough) {
    58. startAnimation();
    59. } else {
    60. reset();
    61. }
    62. }
    63. }
    64. @override
    65. void dispose() {
    66. _animationController.dispose();
    67. _offsetController.dispose();
    68. super.dispose();
    69. }
    70. @override
    71. Widget build(BuildContext context) {
    72. return GestureDetector(
    73. onTap: () {
    74. if (widget.strikethrough) {
    75. widget.onChange?.call(false);
    76. } else {
    77. widget.onChange?.call(true);
    78. }
    79. },
    80. child: SlideTransition(
    81. position: _offsetAnimation,
    82. child: Stack(
    83. children: [
    84. AnimatedBuilder(
    85. animation: _animationController,
    86. builder: (context, child) {
    87. return Text(
    88. widget.text,
    89. maxLines: 1,
    90. softWrap: false,
    91. style: widget.textStyle.copyWith(
    92. color: _animationColor.value,
    93. overflow: TextOverflow.clip,
    94. ),
    95. );
    96. }),
    97. // AnimatedDefaultTextStyle(
    98. // style: widget.textStyle..copyWith(color: _animationColor.value),
    99. // duration: const Duration(milliseconds: 500),
    100. // child: Text(widget.text),
    101. // ),
    102. AnimatedBuilder(
    103. animation: _animation,
    104. builder: (context, child) {
    105. return Positioned(
    106. left: 0,
    107. right: 0,
    108. top: 0,
    109. bottom: 0,
    110. child: CustomPaint(
    111. painter: StrikeThroughTextPainter(
    112. width: TextSizeBox.fromText(widget.text,
    113. textStyle: widget.textStyle)
    114. .width *
    115. _animation.value,
    116. height: 2.0,
    117. color: widget.inactiveTextColor ?? Colors.grey,
    118. ),
    119. ),
    120. );
    121. },
    122. ),
    123. ],
    124. ),
    125. ),
    126. );
    127. }
    128. void startAnimation() async {
    129. _animationController.reset();
    130. await _offsetController.forward();
    131. await _offsetController.reverse();
    132. _animationController.forward();
    133. }
    134. void reset() {
    135. _animationController.reset();
    136. }
    137. }
    138. class StrikeThroughTextPainter extends CustomPainter {
    139. final double width;
    140. final double height;
    141. final Color color;
    142. StrikeThroughTextPainter(
    143. {required this.width, required this.height, required this.color});
    144. @override
    145. void paint(Canvas canvas, Size size) {
    146. final paint = Paint()
    147. ..color = color
    148. ..strokeWidth = height
    149. ..strokeCap = StrokeCap.round;
    150. if (width > 0) {
    151. canvas.drawLine(
    152. Offset(0, size.height / 2),
    153. Offset(width > size.width ? size.width : width, size.height / 2),
    154. paint);
    155. }
    156. }
    157. @override
    158. bool shouldRepaint(StrikeThroughTextPainter oldDelegate) {
    159. return width != oldDelegate.width || height != oldDelegate.height;
    160. }
    161. }
    162. class TextSizeBox {
    163. final double width;
    164. final double height;
    165. TextSizeBox({required this.width, required this.height});
    166. factory TextSizeBox.fromText(String text, {TextStyle? textStyle}) {
    167. final TextPainter textPainter = TextPainter(
    168. text: TextSpan(text: text, style: textStyle),
    169. maxLines: 1,
    170. textDirection: TextDirection.ltr,
    171. )..layout(minWidth: 0, maxWidth: double.infinity);
    172. return TextSizeBox(width: textPainter.width, height: textPainter.height);
    173. }
    174. }

    以上就是怎么使用Flutter StrikeThroughTextAnimation实现文字中划线动画的详细内容,更多关于怎么使用Flutter StrikeThroughTextAnimation实现文字中划线动画的资料请关注九品源码其它相关文章!