美文网首页
Flutter 重复造轮子 (8) Marquee 跑马灯文字显

Flutter 重复造轮子 (8) Marquee 跑马灯文字显

作者: 半城半离人 | 来源:发表于2023-09-16 11:29 被阅读0次

详细可以访问仓库 HcUi: 重复创造Flutter 的轮子 在原有组件上拓展 展现出新的特性 (gitee.com)

介绍

文字跑马灯组件可以定义文字移动方向 时长等设置


dfef8d7d-c1af-4a9b-96fc-27d2dc9eae5a.gif

代码演示

基础用法

HcMarquee(
                content:
                    "青年的命运,从来都同时代紧密相连。新民主主义革命时期,青春是漫漫长征路上那一声坚定的“跟着走”,走向柳暗花明的胜利之路;”

修改滚动时长

    HcMarquee(
              duration: Duration(milliseconds: 60000),
                            content:
                    "青年的命运,从来都同时代紧密相连。新民主主义革命时期,青春是漫漫长征路上那一声坚定的“跟着走”,走向柳暗花明的胜利之路;",

          ),

修改滚动方向

如果纵向传入 content 则按照长度自动截断成组

         HcMarquee(
                scrollDirection: Axis.vertical,
                content:
                    "青年的命运,从来都同时代紧密相连。新民主主义革命时期,青春是漫漫长征路上那一声坚定的“跟着走”,走向柳暗花明的胜利之路;",

自定义滚动组

            HcMarquee(
              scrollDirection: Axis.vertical,
              contentList: [
                "君不见黄河之水天上来,奔流到海不复回。",
                "君不见高堂明镜悲白发,朝如青丝暮成雪。",
                " 人生得意须尽欢,莫使金樽空对月。",
                " 天生我材必有用,千金散尽还复来。",
                " 烹羊宰牛且为乐,会须一饮三百杯。",
                " 岑夫子,丹丘生,将进酒,杯莫停。",
                "与君歌一曲,请君为我倾耳听。",
                " 钟鼓馔玉不足贵,但愿长醉不复醒。",
                " 古来圣贤皆寂寞,惟有饮者留其名。",
                "陈王昔时宴平乐,斗酒十千恣欢谑。",
                "主人何为言少钱,径须沽取对君酌。",
                "五花马、千金裘,呼儿将出换美酒,与尔同销万古愁。"
              ],
            ),

不允许自动滚动

            HcMarquee(
                enableScroll: false,
                content:
                    "青年的命运,从来都同时代紧密相连。新民主主义革命时期,青春是漫漫长征路上那一声坚定的“跟着走”,走向柳暗花明的胜利之路;"),

不允许自动滚动也不允许手动滑动

           HcMarquee(
                enableScroll: false,
                physics: NeverScrollableScrollPhysics(),
                content:
                    "青年的命运,从来都同时代紧密相连。新民主主义革命时期,青春是漫漫长征路上那一声坚定的“跟着走”,走向柳暗花明的胜利之路;"),

文字不满一屏也强制滚动

            HcMarquee(
              content: "青年的命运,从来都同时代紧密相连。",
              forceScroll: true,
            ),

API

props

参数 说明 类型 默认值 是否必填
enableScroll 是否允许滚动 bool true true
forceScroll 横向不满一屏时是否强制滚动 bool true true
duration 滚动时长 int 20000 true
scrollDirection 滚动方向 Axis Axis.horizontal true
content 内容文字 String "" false
contentList 纵向滚动时 文字组 List<String>? - false
textStyle 文字样式 TextStyle - false
physics 手动滚动时样式 ScrollPhysics - false
overflow 纵向滚动文字组超出样式 TextOverflow ellipsis false
textAlign 纵向滚动文字对齐方向 TextAlign TextAlign.start false

项目源码

String _defaultText = "未设置滚动文字!!!";

class HcMarquee extends StatefulWidget {
  //滚动文字
  final String? content;

  //纵向滚动的文字组
  final List<String>? contentList;

  //滚动方向
  final Axis scrollDirection;

  //滚动总时长
  final Duration duration;

  //文字样式
  final TextStyle textStyle;

  //是否允许滚动
  final bool enableScroll;

  //不满全屏时是否强制滚动 仅用在横向
  final bool forceScroll;

  // 滚动效果 仅用在不自动滑动时
  final ScrollPhysics? physics;

  //仅用在自定义文字组 纵向滑动时
  final TextOverflow overflow;

  //仅用在纵向滑动时文字对齐方式
  final TextAlign textAlign;

  const HcMarquee({
    Key? key,
    this.content,
    this.contentList,
    this.scrollDirection = Axis.horizontal,
    this.duration = const Duration(milliseconds: 10000),
    this.textStyle = const TextStyle(color: Colors.black, fontSize: 16),
    this.enableScroll = true,
    this.forceScroll = false,
    this.physics,
    this.overflow = TextOverflow.ellipsis,
    this.textAlign = TextAlign.start,
  }) : super(key: key);

  @override
  State<HcMarquee> createState() => _HcMarqueeState();
}

class _HcMarqueeState extends State<HcMarquee>
    with SingleTickerProviderStateMixin {
  //获取组件最大宽度与高度
  Size _viewSize = const Size(0, 0);

  //动画的控制器
  late AnimationController _animationController;

  //动画
  late Animation<Offset> _animation;

  //滚动控制器
  final ScrollController _scrollController = ScrollController();

  @override
  void initState() {
    super.initState();

    _animationController =
        AnimationController(vsync: this, duration: widget.duration);
    _animation = Tween(begin: const Offset(0, 0), end: const Offset(1, 0))
        .animate(_animationController);
    _animation.addListener(() {
      //如果有子组件
      if (_scrollController.hasClients) {
        if (_scrollController.position.hasContentDimensions) {
          if (widget.scrollDirection == Axis.horizontal) {
            //前后两端内容+空白区域
            double fullLength = _scrollController.position.maxScrollExtent +
                2 * _viewSize.width;
            _scrollController
                .jumpTo(_animation.value.dx * fullLength - _viewSize.width);
          } else {
            _scrollController.jumpTo(_animation.value.dx *
                _scrollController.position.maxScrollExtent);
          }
        }
      }
    });
  }

  @override
  void dispose() {
    _scrollController.dispose();
    _animationController.dispose();
    super.dispose();
  }

  @override
  void didUpdateWidget(covariant HcMarquee oldWidget) {
    // TODO: implement didUpdateWidget
    super.didUpdateWidget(oldWidget);
    if (oldWidget != widget) {
      _animationController.duration = widget.duration;
    }
  }

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(builder: (context, size) {
      _viewSize = Size(size.maxWidth, size.maxHeight);
      String content = widget.content ?? _defaultText;
      List<String> contentList = widget.contentList ?? [];
      Widget child = Container();
      if (widget.scrollDirection == Axis.horizontal) {
        child = _buildHorizontalView(contentList, content, size);
      } else {
        child = _buildVerticalView(contentList, content, size);
      }
      return child;
    });
  }

  //构建纵向列表
  Widget _buildVerticalView(
      List<String> contentList, String content, BoxConstraints size) {
    if (widget.contentList != null && widget.contentList!.isNotEmpty) {
      contentList = widget.contentList!;
    } else {
      contentList = [];
      while (content.isNotEmpty) {
        TextPainter painter = HcStringUtil.getTextInfo(
            content, widget.textStyle,
            maxLines: 1, width: _viewSize.width);
        int maxIndex =
            painter.getPositionForOffset(Offset(_viewSize.width, 0)).offset;
        contentList.add(content.substring(0, min((maxIndex), content.length)));
        content = content.substring(min(maxIndex, content.length));
      }
    }
    TextPainter painter =
        HcStringUtil.getTextInfo(_defaultText, widget.textStyle);
    //如果行数大于一滚动
    if (contentList.length > 1 && widget.enableScroll) {
      _animationController.repeat();
    } else {
      _animationController.stop();
    }
    return ConstrainedBox(
      constraints: BoxConstraints(
          minWidth: _viewSize.width,
          minHeight: painter.height,
          maxHeight: painter.height),
      child: ListView.builder(
          physics: widget.physics,
          itemBuilder: (context, index) => Text(
                contentList[index],
                textAlign: widget.textAlign,
                style: widget.textStyle,
                maxLines: 1,
                overflow: widget.overflow,
              ),
          shrinkWrap: true,
          controller: _scrollController,
          itemCount: contentList.length,
          scrollDirection: Axis.vertical),
    );
  }

  //构建横向滚动组件
  SingleChildScrollView _buildHorizontalView(
      List<String> contentList, String content, BoxConstraints size) {
    if (widget.content != null) {
      content = widget.content!;
    } else if (widget.contentList != null) {
      content = contentList.join("");
    }

    TextPainter painter = HcStringUtil.getTextInfo(content, widget.textStyle,
        width: _viewSize.width, maxLines: 1);
    if (widget.enableScroll &&
        (!painter.didExceedMaxLines && widget.forceScroll ||
            painter.didExceedMaxLines)) {
      _animationController.repeat();
    } else {
      _animationController.stop();
    }
    return SingleChildScrollView(
      controller: _scrollController,
      scrollDirection: Axis.horizontal,
      child: ConstrainedBox(
          constraints: BoxConstraints(minWidth: _viewSize.width),
          child: Text(
            content,
            style: widget.textStyle,
          )),
    );
  }
}

  /// 获取文字信息
  static TextPainter getTextInfo(String text, TextStyle style,
      {int maxLines = 2 ^ 23,
      double width = double.infinity,
      String? ellipsis,
      textDirection = TextDirection.ltr}) {
    TextSpan span = TextSpan(text: text, style: style);
    return TextPainter(
      text: span,
      maxLines: maxLines,
      ellipsis: ellipsis,
      textDirection: textDirection,
    )..layout(maxWidth: width);
  }

相关文章