验证码: 看不清楚,换一张 查询 注册会员,免验证
  • {{ basic.site_slogan }}
  • 打开微信扫一扫,
    您还可以在这里找到我们哟

    关注我们

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

阅读:766 来源:乙速云 作者:代码code

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

      基本使用

      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来控制左右移动的偏移量

       _offsetController = AnimationController(
            vsync: this,
            duration: const Duration(milliseconds: 100),
          );
      _offsetAnimation = Tween(
            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
          with TickerProviderStateMixin {
        late AnimationController _animationController;
        late Animation _animation;
        late Animation _animationColor;
        late AnimationController _offsetController;
        late Animation _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(
            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);
        }
      }
    分享到:
    *特别声明:以上内容来自于网络收集,著作权属原作者所有,如有侵权,请联系我们: hlamps#outlook.com (#换成@)。
    相关文章
    {{ v.title }}
    {{ v.description||(cleanHtml(v.content)).substr(0,100)+'···' }}
    你可能感兴趣
    推荐阅读 更多>