Flutter 重复造轮子 (9) Ellipsis 省略文字组件 的封装

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

介绍

对长文本进行省略展示,支持展开/收起,对省略位置可以自定义


c2b2bc33-ed77-4ad3-b50d-51d003bf5c52.gif

代码演示

基础用法

      HcTextEllipsis(
            content:"生态文明建设是关系中华民族永续发展的根本大计。总书记指出,要把建设美丽中国摆在强国建设、民族复兴的突出位置,以高品质生态环境支撑高质量发展,加快推进人与自然和谐共生的现代化。各地坚持以习近平生态文明思想为指引,牢固树立和践行绿水青山就是金山银山的理念,以更高站位、更宽视野、更大力度,全面推进美丽中国建设。"
          ),

最大显示行数

          HcTextEllipsis(
            maxLines: 2,
            content:"生态文明建设是关系中华民族永续发展的根本大计。总书记指出,要把建设美丽中国摆在强国建设、民族复兴的突出位置,以高品质生态环境支撑高质量发展,加快推进人与自然和谐共生的现代化。各地坚持以习近平生态文明思想为指引,牢固树立和践行绿水青山就是金山银山的理念,以更高站位、更宽视野、更大力度,全面推进美丽中国建设。"
          ),

省略符位置

 HcTextEllipsis(
            maxLines: 2,
            position: HcTextEllipsisPosition.center,
            content:"生态文明建设是关系中华民族永续发展的根本大计。总书记指出,要把建设美丽中国摆在强国建设、民族复兴的突出位置,以高品质生态环境支撑高质量发展,加快推进人与自然和谐共生的现代化。各地坚持以习近平生态文明思想为指引,牢固树立和践行绿水青山就是金山银山的理念,以更高站位、更宽视野、更大力度,全面推进美丽中国建设。"
          ),

替换省略符

          HcTextEllipsis(
            maxLines: 2,
            ellipsisDots: "~~~",
            position: HcTextEllipsisPosition.center,
            content:"生态文明建设是关系中华民族永续发展的根本大计。总书记指出,要把建设美丽中国摆在强国建设、民族复兴的突出位置,以高品质生态环境支撑高质量发展,加快推进人与自然和谐共生的现代化。各地坚持以习近平生态文明思想为指引,牢固树立和践行绿水青山就是金山银山的理念,以更高站位、更宽视野、更大力度,全面推进美丽中国建设。"
          ),

展开按钮单独显示

          HcTextEllipsis(
            maxLines: 2,
            ellipsisDots: "~~~",
            separateShow: false,
            position: HcTextEllipsisPosition.center,
            content:"生态文明建设是关系中华民族永续发展的根本大计。总书记指出,要把建设美丽中国摆在强国建设、民族复兴的突出位置,以高品质生态环境支撑高质量发展,加快推进人与自然和谐共生的现代化。各地坚持以习近平生态文明思想为指引,牢固树立和践行绿水青山就是金山银山的理念,以更高站位、更宽视野、更大力度,全面推进美丽中国建设。"
          ),

点击按钮的回调

       HcTextEllipsis(
              maxLines: 5,
              ellipsisDots: "...",
              separateShow: false,
              showEllipsisBtn: true,
              position: HcTextEllipsisPosition.center,
              callback: (bool state) {
                //设定回调后手动控制是否展开 返回true为展开 false为不展开
                return Future.value(!state);
              },
              content:
                  "生态文明建设是关系中华民族永续发展的根本大计。总书记指出,要把建设美丽中国摆在强国建设、民族复兴的突出位置,以高品质生态环境支撑高质量发展,加快推进人与自然和谐共生的现代化。各地坚持以习近平生态文明思想为指引,牢固树立和践行绿水青山就是金山银山的理念,以更高站位、更宽视野、更大力度,全面推进美丽中国建设。"),

API

props

参数 说明 类型 默认值 是否必填
maxLines 需要展示的行数 int 1 true
content 文字内容 String - true
expandText 展开按钮的文字内容 String 展开 true
collapseText 收起按钮的文字内容 String 收起 true
ellipsisDots 省略符 String ... true
position 分隔符的位置 HcTextEllipsisPosition end true
isExpand 默认是否展开 bool false true
showEllipsisBtn 是否展示更多的按钮 bool false true
separateShow 按钮是否单独展示 bool false true
contentStyle 文字的字体样式 TextStyle? - false
btnTextStyle 按钮的文字样式 TextStyle? - false
callback 点击的时候回调 HcTextEllipsisCallback - false

HcTextEllipsisPosition

展示省略符号位置

参数名 说明
start 开头
center 中部
end 结尾

Function

方法名 说明 参数 返回类型
HcTextEllipsisCallback 点击按钮后的回调 Function(bool) Future<bool?>

项目源码

enum HcTextEllipsisPosition { start, center, end }

typedef HcTextEllipsisCallback = Future<bool?> Function(bool);

class HcTextEllipsis extends StatefulWidget {
  //需要展示的行数
  final int maxLines;

  //文字内容
  final String content;

  //展开的文字
  final String expandText;

  //折叠的文字
  final String collapseText;

  ///分隔符
  final String ellipsisDots;

  /// 文本的样式
  final TextStyle contentStyle;

  //按钮的 文字样式
  final TextStyle btnTextStyle;

  //点击按钮的回调
  final HcTextEllipsisCallback? callback;

  //分割符的位置
  final HcTextEllipsisPosition position;

  //是否展开
  final bool isExpand;

  //是否单独显示
  final bool separateShow;

  //是否展示拓展按钮
  final bool showEllipsisBtn;

  const HcTextEllipsis(
      {Key? key,
      this.isExpand = false,
      this.maxLines = 1,
      required this.content,
      this.expandText = "展开",
      this.collapseText = "收起",
      this.ellipsisDots = "...",
      this.position = HcTextEllipsisPosition.end,
      this.contentStyle = const TextStyle(color: Colors.black, fontSize: 16),
      this.callback,
      this.btnTextStyle = const TextStyle(
          color: Colors.blue, fontSize: 14, fontWeight: FontWeight.w500),
      this.separateShow = false,
      this.showEllipsisBtn = true})
      : super(key: key);

  @override
  State<HcTextEllipsis> createState() => _HcTextEllipsisState();
}

class _HcTextEllipsisState extends State<HcTextEllipsis> {
  //是否展开
  bool _isExpand = false;

  //组件的大小
  Size _viewSize = const Size(0, 0);

  //按钮组件的宽度
  double btnWidth = 0.0;

  //省略符号的宽度
  double dotWidth = 0.0;

  //文字高度
  double fontHeight = 0.0;

  //ellipsisLineWidth;
  double ellipsisLineWidth = 0.0;

  @override
  void initState() {
    super.initState();
    _isExpand = widget.isExpand;
  }

  @override
  void didUpdateWidget(covariant HcTextEllipsis oldWidget) {
    super.didUpdateWidget(oldWidget);
    _isExpand = widget.isExpand;
  }

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(builder: (context, size) {
      //按钮的宽度
      btnWidth =
          HcStringUtil.boundingTextSize(widget.expandText, widget.btnTextStyle)
              .width;
      //文字的宽度与高度
      Size fontSize = HcStringUtil.boundingTextSize(
          widget.ellipsisDots, widget.contentStyle);
      dotWidth = fontSize.width;
      fontHeight = fontSize.height;
      //组件最大长度
      _viewSize = Size(size.maxWidth, size.maxHeight);
      //获取painter信息
      TextPainter painter = HcStringUtil.getTextInfo(
          widget.content, widget.contentStyle,
          maxLines: widget.maxLines, width: _viewSize.width);

      // 如果不满一行直接显示
      if (!painter.didExceedMaxLines) {
        return Text(widget.content, style: widget.contentStyle);
      }
      String result = "";
      String content = widget.content;
      switch (widget.position) {
        case HcTextEllipsisPosition.start:
          result = _buildEllipsisStartText(content, widget.maxLines);
          break;
        case HcTextEllipsisPosition.center:
          if (widget.maxLines == 1) {
            ellipsisLineWidth = _viewSize.width -
                (!widget.separateShow && widget.showEllipsisBtn
                    ? btnWidth
                    : 0) -
                dotWidth;
            int start = HcStringUtil.getTextInfo(content, widget.contentStyle,
                    width: (ellipsisLineWidth ~/ 2).toDouble())
                .getPositionForOffset(
                    Offset((ellipsisLineWidth ~/ 2).toDouble(), 0))
                .offset;
            result = content.substring(0, start);
            result += widget.ellipsisDots;
            content = HcStringUtil.reverseString(content);
            int end = HcStringUtil.getTextInfo(content, widget.contentStyle,
                    width:
                        ellipsisLineWidth - (ellipsisLineWidth ~/ 2).toDouble())
                .getPositionForOffset(Offset(
                    ellipsisLineWidth - (ellipsisLineWidth ~/ 2).toDouble(), 0))
                .offset;
            result += HcStringUtil.reverseString(content.substring(0, end));
          } else {
            double halfLine = (widget.maxLines ~/ 2).toDouble();
            result += _buildEllipsisEndText(content, widget.maxLines - halfLine,
                showEllipsis: false);
            result += _buildEllipsisStartText(content, halfLine);
          }
          break;
        case HcTextEllipsisPosition.end:
          result = _buildEllipsisEndText(content, widget.maxLines);
          break;
      }

      return RichText(
        text: TextSpan(
            text: _isExpand ? widget.content : result,
            style: widget.contentStyle,
            children: [if (widget.showEllipsisBtn) _ellipsisBtn()]),
      );
    });
  }

  InlineSpan _ellipsisBtn() {
    return TextSpan(
        text: _isExpand ? widget.collapseText : widget.expandText,
        style: widget.btnTextStyle,
        recognizer: TapGestureRecognizer()
          ..onTap = () async {
            bool isExpand = !_isExpand;
            if (widget.callback != null) {
              try {
                isExpand = await widget.callback!.call(_isExpand) ?? isExpand;
              } finally {}
            }
            setState(() {
              _isExpand = isExpand;
            });
          });
  }

  String _buildEllipsisStartText(content, maxLines) {
    String result = "";
    // 反转文字截取最后
    content = HcStringUtil.reverseString(content);
    // 获取文字展示宽度
    ellipsisLineWidth = _viewSize.width -
        (!widget.separateShow && widget.showEllipsisBtn ? btnWidth : 0) -
        (widget.maxLines == 1 ? dotWidth : 0);
    // 获取阶段位置
    int lastLineIndex = HcStringUtil.getTextInfo(content, widget.contentStyle,
            width: _viewSize.width)
        .getPositionForOffset(Offset(ellipsisLineWidth, 0))
        .offset;
    // 判断阶段位置展示的文字是否超过最大限度
    double resultWidth = HcStringUtil.boundingTextSize(
            content.substring(0, lastLineIndex), widget.contentStyle)
        .width;
    // 修正最大截取距离
    lastLineIndex =
        resultWidth > ellipsisLineWidth ? lastLineIndex - 1 : lastLineIndex;
    String temp = content.substring(0, lastLineIndex);
    if (maxLines > 1) {
      content = content.substring(lastLineIndex);
      if (maxLines > 2) {
        int centerIndex = HcStringUtil.getTextInfo(content, widget.contentStyle,
                width: _viewSize.width)
            .getPositionForOffset(
                Offset(_viewSize.width, fontHeight * (maxLines - 3)))
            .offset;
        temp += content.substring(0, centerIndex);

        content = content.substring(centerIndex);
      }
      int firstIndex = HcStringUtil.getTextInfo(content, widget.contentStyle,
              width: _viewSize.width - dotWidth)
          .getPositionForOffset(Offset(_viewSize.width - dotWidth, 0))
          .offset;
      temp += content.substring(0, firstIndex);
    }

    result = widget.ellipsisDots;
    result += HcStringUtil.reverseString(temp);
    return result;
  }

  String _buildEllipsisEndText(content, maxLines, {bool showEllipsis = true}) {
    String result = "";
    if (maxLines > 1) {
      int position = HcStringUtil.getTextInfo(content, widget.contentStyle,
              width: _viewSize.width)
          .getPositionForOffset(
              Offset(_viewSize.width, fontHeight * (maxLines - 2)))
          .offset;
      result = content.substring(0, position);
      content = content.substring(position);
    }
    ellipsisLineWidth = _viewSize.width -
        (showEllipsis && (!widget.separateShow && widget.showEllipsisBtn)
            ? btnWidth
            : 0) -
        dotWidth;
    int position = HcStringUtil.getTextInfo(content, widget.contentStyle,
            width: ellipsisLineWidth)
        .getPositionForOffset(Offset(ellipsisLineWidth, 0))
        .offset;
    result += content.substring(0, position);
    if (showEllipsis) {
      result += widget.ellipsisDots;
    }
    return result;
  }
}
 static Size boundingTextSize(String text, TextStyle style,
      {int maxLines = 2 ^ 31,
      double maxWidth = double.infinity,
      textDirection = TextDirection.ltr}) {
    if (text.isEmpty) {
      return Size.zero;
    }
    final TextPainter textPainter = TextPainter(
        textDirection: textDirection,
        text: TextSpan(text: text, style: style),
        maxLines: maxLines)
      ..layout(maxWidth: maxWidth);
    return textPainter.size;
  }

  /// 获取文字信息
  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);
  }

  ///翻转文字
  static String reverseString(String str) {
    return str.split("").reversed.join();
  }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容