欢迎访问移动开发之家(rcyd.net),关注移动开发教程。移动开发之家  移动开发问答|  每日更新
页面位置 : > > > 内容正文

Flutter 文字中划线动画StrikeThroughTextAnimation,

来源: 开发者 投稿于  被查看 23767 次 评论:50

Flutter 文字中划线动画StrikeThroughTextAnimation,


目录
  • 概述
  • 效果预览
  • 基本使用
  • 实现
    • 1、布局
    • 2、绘制中划线
    • 3、动画
  • 完整代码

    概述

    接上文 CheckBoxAnimation 动画,在加上文字的动画,刚好可以做一个组合的列表动画。文字部分动画主要就是左右移动、颜色变化以及在绘制文字中划线。

    效果预览

    StrikeThroughText_Preview.gif

    基本使用

    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 文字中划线的资料请关注3672js教程其它相关文章!

    您可能感兴趣的文章:
    • Flutter GetX使用实例详细解读
    • 大前端代码重构之事件拦截iOS Flutter Vue示例分析
    • Flutter开发Mac桌面应用实现自动提取生成视频字幕文件
    • Flutter LinearProgressIndicator使用指南分析
    • MaterialApp Flutter 应用全局配置与主题管理详解
    • Flutter学习之矢量图SVG的区域填色示例详解

    用户评论