全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2024-12-16_「转」史上最强大的文本溢出效果,强得飞起 !

您的位置:首页 >> 新闻 >> 行业资讯

史上最强大的文本溢出效果,强得飞起 ! 点击关注公众号,“技术干货”及时达! 前言ExtendedText 的主要功能是支持自定义文本溢出效果。ExtendedText 作为 5 年前在 Flutter 平台发布的组件库,可以说是陪伴了一代代 Flutterer 的成长。前段时间,ExtendedText 也增加了鸿蒙纯血 Next 系统的原生支持。 这里再说下什么是 自定义文本溢出效果 ? 溢出效果的自定义,即希望溢出的效果不是单调的 ...,而且可以指定任何组件。对比其他平台,该系果的支持情况如下: 平台ellipsis 自定义android不支持Ios不支持web不支持flutter不支持(ExtendedText 支持)鸿蒙 Nextellipsis (ExtendedText 支持)溢出效果的位置,即在开头,中间,还是结尾。对比其他平台,该效果的支持情况如下: 平台开头中间结尾androidandroid:ellipsize = "start"android:ellipsize = "middle"android:ellipsize = "end"IosNSLineBreakByTruncatingHeadNSLineBreakByTruncatingMiddleNSLineBreakByTruncatingTailwebtext-overflow: ellipsis clip不支持text-overflow: clip ellipsisflutter不支持(ExtendedText 支持)不支持(ExtendedText 支持)TextOverflow.ellipsis鸿蒙 NextEllipsisMode.START (ExtendedText 支持)EllipsisMode.MIDDLE (ExtendedText 支持)EllipsisMode.END但是需求往往在不经意间就会出现,有用户在评论区问, 支持高亮关键字加两端省略吗? 并且附上了一张图片。 1026b7b3b3804e60bdd2cb43ac4d8ce1~tplv-k3u1fbpfcp-jj-mark-v1_0_0_0_0_5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5p2o5rC45a6J_q75_副本.webp就是手机短信里面的搜索功能,我自己也看了一下,总结下要求: 搜索的词语,高亮,即保证它要出现在文本之中根据高亮文本的位置,来设置文本溢出效果。如果高亮文字在中间,那么开头和结尾都显示溢出效果;如果高亮文字在前面可以完全显示,那么最后结尾溢出效果;如果高亮文字在文字后面,那么开头显示溢出效果。由于溢出效果的位置完全是依据高亮文本的位置而定的,当前 ExtendedText 的功能并不能支持,所以新增了新的溢出模式 TextOverflowPosition.auto。 export enum TextOverflowPosition { /// 开头 start, /// 中间 middle, /// 结尾 end, /// 确保 keepVisible Span 可见 /// 自动调整溢出位置 auto,}最终实现效果如下图,也支持多行显示。 实现打开冰箱放进大象关上冰箱跟将一个大象放进冰箱一样简单,做出文本溢出效果只需要下面 4 步。 裁剪文本计算文本不溢出的情况绘制溢出效果,并且遮蔽下层的文字计算文本不溢出的情况Flutter 端我们可以在 performLayout 方法中通过不断尝试裁剪 TextPainter 的 InlineSpan,通过以下方法,判断文本是否溢出。 bool _didVisualOverflow({TextPainter? textPainter}) { final Size textSize = (textPainter ?? _textPainter).size; final bool textDidExceedMaxLines = (textPainter ?? _textPainter).didExceedMaxLines; final bool didOverflowHeight = size.height textSize.height || textDidExceedMaxLines; final bool didOverflowWidth = size.width textSize.width; if (size.height textSize.height) { size = constraints.constrain(textSize); } return didOverflowWidth || didOverflowHeight; }在鸿蒙端,我们可以在 onMeasureSize 方法中通过不断调整裁剪 ParagraphBuilder 的内容,通过以下方法,判断文本是否溢出。_didVisualOverflow(paragraph: text.Paragraph, constraint: ConstraintSizeOptions): boolean { let textSize: SizeResult = { width: px2vp(paragraph.getMaxWidth()), height: px2vp(paragraph.getHeight()), }; let size: SizeResult = { width: constraint.maxWidth! as number, height: constraint.maxHeight! as number, } let textDidExceedMaxLines = paragraph.didExceedMaxLines(); let didOverflowHeight = size.height textSize.height || textDidExceedMaxLines; let didOverflowWidth = size.width textSize.width; let hasVisualOverflow = didOverflowWidth || didOverflowHeight; return hasVisualOverflow;}裁剪文本通过上面一步,我们可以计算出一个临界值,考虑到有复制选择功能,裁剪掉的文本不能直接丢弃,这里利用到SpecialTextSpan。 ?你见到的并不是真实的 ? SpecialTextSpan( 'abef', actualText: 'abcdef', );比如abcdef, 我们找到的Range为[2,3],即最终显示ab...ef。考虑支持选择复制,所以我们这里不能简单丢掉cd。 maxIndex 为文本的长度,找到文本不溢出的和溢出临界点index。根据溢出位置可以分为下面 4 种情况。 start[0,offset] 区域的文本都需要被裁剪掉,即 [0,index] 舍弃, [index,maxIndex] 显示。 middle[m,index] 区域的文本都需要被裁剪掉,其中 m 为溢出效果区域左侧的索引位置。 [0,m] 显示; [m,index] 舍弃(这里绘制溢出效果); [index,maxIndex]显示。 end无需更多计算 auto如果高亮文本在 [offset,max] 区域能显示,这种情况就相当于 start 的情况; 如果高亮文本在 [0,offset] 区域能显示,这种情况就相当于 end 的情况; 如果上面 2 种情况都不满足,即前面需要裁剪,后面也需要裁剪,那么我们裁剪的区域左边一部分和右边一部分,中间的部分要保证高亮文本可见。 要使用该功能,首先需要将高亮文本(可见)对于的 Span 的 keepVisible 设置成 true 。后续在计算中,我们就可以找到它,然后确定高亮文本(可见)的范围,进行进一步的处理。 鸿蒙 端寻找高亮文本(可见)的代码如下: let keepVisibleSpan: InlineSpan | null = null;this.text.visitChildren((span) = { if (span.keepVisible === true) { keepVisibleSpan = span; return false; } return true;})Flutter 端寻找高亮文本(可见)的代码如下: SpecialInlineSpanBase? keepVisibleSpan; text.visitChildren((InlineSpan span) { if (span is SpecialInlineSpanBase && (span as SpecialInlineSpanBase).keepVisible == true) { keepVisibleSpan = span as SpecialInlineSpanBase; return false; } return true; });绘制溢出效果,并且消除下层的文字start绘制在第一行的最左边。 middle如果总行数是奇数的话,绘制在中间的一行的正中间;如果总行数是偶数的话,绘制在(总行数除以 2)+ 1 行的最左边。 end绘制在最后一行的最右边。 auto分为三种情况。绘制在第一行的最左边;绘制在最后一行的最右边;或者绘制在第一行的最左边以及最后一行的最右边。 消除下层文字除了绘制溢出效果,我们还要注意一点,那就是将溢出效果下面的文字可以消除掉。具体方式为 Flutter 端通过 canvas 的 clipRect 方法,在绘制文字之前裁剪掉那部分的区域。 // zmtzawqlp // clip rect of over flow if (_overflowRects != null) { context.canvas.saveLayer(offset & size, Paint()); if (overflowWidget?.clearType == TextOverflowClearType.clipRect) { if (_overflowClipTextRects != null) { for (final Rect rect in _overflowClipTextRects!) { context.canvas.clipRect( rect.shift(offset), clipOp: ui.ClipOp.difference, ); } } if (_overflowRects != null) { for (final Rect rect in _overflowRects!) { context.canvas.clipRect( rect.shift(offset), clipOp: ui.ClipOp.difference, ); } } } } _textPainter.paint(context.canvas, offset); paintInlineChildren(context, offset); // zmtzawqlp if (_overflowRects != null) { context.canvas.restore(); } // zmtzawqlp _paintTextOverflow(context, offset);鸿蒙 端通过 canvas 的 clipRect 方法,在绘制文字之前裁剪掉那部分的区域。 if (this.overflowClipRects.length != 0) { context.canvas.saveLayer(); for (let index = 0; index this.overflowClipRects.length; index++) { const overflowClipRect = this.overflowClipRects[index]; context.canvas.clipRect(overflowClipRect, drawing.ClipOp.DIFFERENCE); }}this.paragraph.paint(context.canvas, 0, 0); if (this.overflowClipRects.length != 0) { context.canvas.restore();}性能再突破之前计算文本不溢出的情况,是以溢出效果的所在区域获取初始的范围,然后利用通过二分查找。实际上,这种算法会造成更多的尝试次数。 我们可以得到一个单行的 TextPainter/Paragraph 配合当前 TextPainter/Paragraph, 用来计算粗略的范围。 假设当前 TextPainter/Paragraph 有 3 行,宽度是 100。单行的 TextPainter/Paragraph 的宽度是 500 。 start那么需要裁剪掉的部分即为 500 - 100 * 3 = 200 。 for (final ui.LineMetrics line in lines) { oneLineWidth -= line.width; } end = ExtendedTextLibraryUtils .convertTextPainterPostionToTextInputPostion( text, oneLineTextPainter.getPositionForOffset(Offset( math.max(oneLineWidth, overflowWidgetSize.width), oneLineTextPainter.height / 2)))! .offset;即可以得到初始的裁剪范围为 0 到 单行 TextPainter/Paragraph 200 位置的 index 。 middle 这里行数是有 3 行,那么中间一行的 index 就是 1 。那么开始的溢出效果左边 x 的位置。而右边为 500 -100 -w 的位置,从后减去每行的宽度,直到 index 1 行溢出的右边。 然后也要考虑偶数行的情况,比如假设为有 4 行. 那么中间一行的 index 就是为 2,那么开始的溢出效果左边 x 的位置。而右边为 500 -100 -w 的位置,从后减去每行的宽度,直到 index 1 行溢出的右边。 final int lineNum = (lines.length / 2).floor(); final bool isEven = lines.length.isEven; final ui.LineMetrics line = lines[lineNum]; double lineTop = 0; for (int index = 0; index lineNum; index++) { final ui.LineMetrics line = lines[index]; lineTop += line.height; } final double lineCenter = lineTop + line.height / 2; ui.Rect overflowRect = Rect.zero; final double textWidth = _textPainter.width; if (isEven) { overflowRect = Rect.fromLTRB( 0, lineCenter - overflowWidgetSize.height / 2, overflowWidgetSize.width, lineCenter + overflowWidgetSize.height / 2, ); } else { overflowRect = Rect.fromLTRB( textWidth / 2 - overflowWidgetSize.width / 2, lineCenter - overflowWidgetSize.height / 2, textWidth / 2 + overflowWidgetSize.width / 2, lineCenter + overflowWidgetSize.height / 2, ); } start = ExtendedTextLibraryUtils .convertTextPainterPostionToTextInputPostion( text, _textPainter .getPositionForOffset(overflowRect.centerRight))! .offset; for (int index = lines.length - 1; index lineNum; index--) { final ui.LineMetrics line = lines[index]; oneLineWidth -= line.width; } oneLineWidth -= line.width - overflowRect.right; end = ExtendedTextLibraryUtils .convertTextPainterPostionToTextInputPostion( text, oneLineTextPainter.getPositionForOffset(Offset( math.max(oneLineWidth, overflowWidgetSize.width), oneLineTextPainter.height / 2)))! .offset;end不需要计算。 auto前面我们找到了高亮文本(可见)。 Flutter 端寻找高亮文本(可见)的代码如下: SpecialInlineSpanBase? keepVisibleSpan; text.visitChildren((InlineSpan span) { if (span is SpecialInlineSpanBase && (span as SpecialInlineSpanBase).keepVisible == true) { keepVisibleSpan = span as SpecialInlineSpanBase; return false; } return true; });通过 keepVisibleSpan 得到了范围 [x1, x2],不管后续怎么裁剪,我们都要保证这个范围在需要保留下来。 _TextRange keepVisibleRange = _TextRange( keepVisibleSpan!.textRange.start, keepVisibleSpan!.textRange.end); final Listui.TextBox rects = oneLineTextPainter.getBoxesForSelection( ExtendedTextLibraryUtils .convertTextInputSelectionToTextPainterSelection( text, TextSelection( baseOffset: keepVisibleRange.start, extentOffset: keepVisibleRange.end), ));这样子我们只需要在 [0,x1] 和 [x2,maxOffset] 之中进行文本裁剪。假设当前 TextPainter/Paragraph 有 3 行,宽度是 100,溢出效果宽度是 20 。 我们以 [x1,x2] 为范围,左右增加当前 TextPainter/Paragraph 的总长度的一半, 注意左右边界,超出的部分反补给另外一端。 final Listui.TextBox rects = oneLineTextPainter.getBoxesForSelection( ExtendedTextLibraryUtils .convertTextInputSelectionToTextPainterSelection( text, TextSelection( baseOffset: keepVisibleRange.start, extentOffset: keepVisibleRange.end), )); double left = double.infinity; double right = 0; for (int index = 0; index rects.length; index++) { final ui.TextBox rect = rects[index]; left = math.min(rect.left, left); right = math.max(rect.right, right); } keepVisibleRange = _TextRange( ExtendedTextLibraryUtils.convertTextPainterPostionToTextInputPostion( text, oneLineTextPainter.getPositionForOffset(Offset( left - overflowWidgetSize.width, oneLineTextPainter.height / 2)))! .offset, ExtendedTextLibraryUtils.convertTextPainterPostionToTextInputPostion( text, oneLineTextPainter.getPositionForOffset(Offset( right + overflowWidgetSize.width, oneLineTextPainter.height / 2)))! .offset, ); final double totalWidth = _textPainter.computeLineMetrics().length * size.width; final double half = math.max( (totalWidth - (right - left)) / 2, overflowWidgetSize.width * 2); left = left - half; right = right + half; if (left 0) { right -= left; left = 0; } final double maxIntrinsicWidth = oneLineTextPainter.width; if (right maxIntrinsicWidth) { left -= right - maxIntrinsicWidth; right = maxIntrinsicWidth; } final _TextRange estimatedRange = _TextRange( ExtendedTextLibraryUtils.convertTextPainterPostionToTextInputPostion( text, oneLineTextPainter.getPositionForOffset( Offset(left, oneLineTextPainter.height / 2)))! .offset, ExtendedTextLibraryUtils.convertTextPainterPostionToTextInputPostion( text, oneLineTextPainter.getPositionForOffset( Offset(right, oneLineTextPainter.height / 2)))! .offset, );性能提升 40% 以上通过估算大概的范围,来替换 二分法 求解,理论上文本越长,性能提升越高。 ?整体性能再突破 40% ! ? 47709FAA7B6829A8DC13EBF43E59EBE1_副本.png使用安装Flutter 端执行 flutter pub add extended_text 鸿蒙 端执行 ohpm install @candies/extended_text 设置可见 Span根据自身的情况,将想要高亮(可见) 的 Span 的 keepVisible 属性设置成 true 。 Flutter 端代码如下: import 'package:extended_text/extended_text.dart';import 'package:flutter/material.dart'; class HighlightText extends RegExpSpecialText { @override RegExp get regExp = RegExp( "Highlight color=['\"](.*?)['\"](.*?)/Highlight", ); static String getHighlightString(String content) { return 'Highlight color="#FF2196F3"' + content + '/Highlight } @override InlineSpan finishText(int start, Match match, {TextStyle? textStyle, SpecialTextGestureTapCallback? onTap}) { final String hexColor = match[1]!; return SpecialTextSpan( text: match[2]!, actualText: match[0], start: start, style: textStyle?.copyWith( color: Color(int.parse(hexColor.substring(1), radix: 16)), ), keepVisible: true, ); }} class HighlightTextSpanBuilder extends RegExpSpecialTextSpanBuilder { @override ListRegExpSpecialText get regExps =RegExpSpecialText[ HighlightText(), ];} 鸿蒙 端代码如下: import * as extended_text from '@candies/extended_text'import { RegExpSpecialTextSpanBuilder, TextSpan } from '@candies/extended_text';import { text } from "@kit.ArkGraphics2D" export class HighlightText extends extended_text.RegExpSpecialText { get regExp(): RegExp { return new RegExp("Highlight color=['"](.*?)['"](.*?)/Highlight", "g"); } static getHighlightString(content: string) { return 'Highlight color="#FF2196F3"' + content + '/Highlight } finishText(start: number, match: RegExpExecArray, context: Context, textStyle?: text.TextStyle,): extended_text.InlineSpan { let color = match[1]; return new TextSpan({ text: match[2], style: { fontSize: vp2px(18), color: extended_text.ColorUtils.stringTo2DColor(color), }, actualText: match[0], start: start, keepVisible: true, }); }} export class HighlightTextSpanBuilder extends RegExpSpecialTextSpanBuilder { get regExps() { return [ new HighlightText(), ]; }}设置溢出位置模式将 TextOverflowWidget 的 position 设置成 TextOverflowPosition.auto 即可。 ExtendedText( searchMessages[index], specialTextSpanBuilder: HighlightTextSpanBuilder(), maxLines: searchText.isEmpty ? 3 : 1, overflowWidget: TextOverflowWidget( child: const Text('\u2026 '), position: TextOverflowPosition.auto, ), );结语 至此,我们在全平台(Web,Android,Ios,Windows, Mac, Linux, HarmonyOS,HyperOS, ColorOS,OriginOS,MagicOS,Chrome OS,?FuchsiaOS)支持了丰富的文本溢出效果。 点击关注公众号,“技术干货”及时达! 阅读原文

上一篇:2017-04-06_原来宫崎骏才是埋彩蛋的高手! 下一篇:2020-11-11_天猫双11订单峰值58.3万笔秒,消费狂欢背后隐藏了哪些技术?

TAG标签:

21
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价