[UGUI]图文混排(五):添加下划线

0.下划线标签

标签格式:<material=underline c=#ffffff h=1 n=*** p=***>blablabla...</material>

material标签会在最后的渲染过程中被自动去除。

1.文字顶点分布

通过打印文字顶点,可以发现顶点是以text控件中的pivot为中心点排序的。如下图,以pivot为中心点建立坐标系,则从1到3,x轴逐渐递增;从1到7,y轴逐渐递减。

并且这些顶点坐标是局部坐标,相对于text中的pivot,无论怎样移动text,打印的坐标都是不变的。

2.下划线的计算

下划线的本质,其实就是在文字底下生成一张图片。因为文字换行的原因,那么最终生成的下划线可以是多条的,即多张图片,而且图片的宽度也是不定的。这里可以将下划线的生成情况分两种来分析。

a.不换行情况。这里先不管minY和maxY,可以看到,下划线的检测点就在start和end之间,其中v1是起始检测点,v2是终点检测点。当v2等于end-2时,检测结束,下划线只有一条,宽度即end-2到v1的长度。

b.换行情况。

当v1在“下”的左下角,v2在“下”的右下角时,无情况发生;

当v1在“下”的左下角,v2在“划”的右下角时,检测到了换行,v2移动到“下”的右下角,在“下”添加一条下划线,同时将v1移动到“划”的左下角;

当v1在“划”的左下角,v2在“线”的右下角时,检测到了换行,并且此时v2处于end-2的检测点,v2移动到“划”的右下角,在“划”添加一条下划线,同时将v1移动到“线”的左下角;

当v1在“线”的左下角,v2在“线”的右下角时,没有检测到换行,在“线”添加一条下划线,最后退出循环。

3.生成下划线

经过CalculateLayoutWithImage这个方法后,可以得到一个去掉了图片标签的字符串,以及对应的顶点列表(包含后缀四个顶点)。利用这个顶点列表,就可以计算出下划线的位置、长度等。

综上,可以得出如下的代码:

  1 using System.Collections.Generic;
  2 using System.Text.RegularExpressions;
  3 using System.Text;
  4 using UnityEngine.EventSystems;
  5 using System;
  6 using UnityEngine;
  7 using UnityEngine.UI;
  8 
  9 //图片<icon name=*** w=1 h=1 n=*** p=***/>
 10 //下划线<material=underline c=#ffffff h=1 n=*** p=***>blablabla...</material>
 11 public class RichText2 : Text {
 12 
 13     private FontData fontData = FontData.defaultFontData;
 14 
 15     //--------------------------------------------------------图片 start
 16     private static readonly string replaceStr = "u00A0";
 17     private static readonly Regex imageTagRegex = new Regex(@"<icon name=([^>s]+)([^>]*)/>");//(名字)(属性)
 18     private static readonly Regex imageParaRegex = new Regex(@"(w+)=([^s]+)");//(key)=(value)
 19     private List<RichTextImageInfo> imageInfoList = new List<RichTextImageInfo>();
 20     private bool isImageDirty = false;
 21     //--------------------------------------------------------图片 end
 22 
 23     //--------------------------------------------------------文字 start
 24     private RichTextParser richTextParser = new RichTextParser();
 25     //--------------------------------------------------------文字 end
 26 
 27     //--------------------------------------------------------事件 start
 28     
 29     //--------------------------------------------------------事件 end
 30 
 31     protected RichText2()
 32     {
 33         fontData = typeof(Text).GetField("m_FontData", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(this) as FontData;
 34     }
 35 
 36     readonly UIVertex[] m_TempVerts = new UIVertex[4];
 37     protected override void OnPopulateMesh(VertexHelper toFill)
 38     {
 39         if (font == null)
 40             return;
 41 
 42         // We don't care if we the font Texture changes while we are doing our Update.
 43         // The end result of cachedTextGenerator will be valid for this instance.
 44         // Otherwise we can get issues like Case 619238.
 45         m_DisableFontTextureRebuiltCallback = true;
 46 
 47         //处理图片标签
 48         string richText = text;
 49         IList<UIVertex> verts = null;
 50         richText = CalculateLayoutWithImage(richText, out verts);
 51 
 52         //处理文字标签
 53         List<RichTextTag> tagList = null;
 54         richTextParser.Parse(richText, out tagList);
 55         for (int i = 0; i < tagList.Count; i++)
 56         {
 57             RichTextTag tag = tagList[i];
 58             switch (tag.tagType)
 59             {
 60                 case RichTextTagType.None:
 61                     break;
 62                 case RichTextTagType.Underline:
 63                     ApplyUnderlineEffect(tag as RichTextUnderlineTag, verts);
 64                     break;
 65                 default:
 66                     break;
 67             }
 68         }
 69 
 70         Vector2 extents = rectTransform.rect.size;
 71 
 72         var settings = GetGenerationSettings(extents);
 73         cachedTextGenerator.Populate(text, settings);
 74 
 75         Rect inputRect = rectTransform.rect;
 76 
 77         // get the text alignment anchor point for the text in local space
 78         Vector2 textAnchorPivot = GetTextAnchorPivot(fontData.alignment);
 79         Vector2 refPoint = Vector2.zero;
 80         refPoint.x = Mathf.Lerp(inputRect.xMin, inputRect.xMax, textAnchorPivot.x);
 81         refPoint.y = Mathf.Lerp(inputRect.yMin, inputRect.yMax, textAnchorPivot.y);
 82 
 83         // Determine fraction of pixel to offset text mesh.
 84         Vector2 roundingOffset = PixelAdjustPoint(refPoint) - refPoint;
 85 
 86         // Apply the offset to the vertices
 87         //IList<UIVertex> verts = cachedTextGenerator.verts;
 88         float unitsPerPixel = 1 / pixelsPerUnit;
 89         //Last 4 verts are always a new line...
 90         int vertCount = verts.Count - 4;
 91 
 92         toFill.Clear();
 93         if (roundingOffset != Vector2.zero)
 94         {
 95             for (int i = 0; i < vertCount; ++i)
 96             {
 97                 int tempVertsIndex = i & 3;
 98                 m_TempVerts[tempVertsIndex] = verts[i];
 99                 m_TempVerts[tempVertsIndex].position *= unitsPerPixel;
100                 m_TempVerts[tempVertsIndex].position.x += roundingOffset.x;
101                 m_TempVerts[tempVertsIndex].position.y += roundingOffset.y;
102                 if (tempVertsIndex == 3)
103                     toFill.AddUIVertexQuad(m_TempVerts);
104             }
105         }
106         else
107         {
108             //Debug.Log(unitsPerPixel);
109             for (int i = 0; i < vertCount; ++i)
110             {
111                 int tempVertsIndex = i & 3;
112                 m_TempVerts[tempVertsIndex] = verts[i];
113                 m_TempVerts[tempVertsIndex].position *= unitsPerPixel;
114                 if (tempVertsIndex == 3)
115                     toFill.AddUIVertexQuad(m_TempVerts);
116                 //Debug.LogWarning(i + "_" + tempVertsIndex + "_" + m_TempVerts[tempVertsIndex].position);
117             }
118         }
119         m_DisableFontTextureRebuiltCallback = false;
120     }
121 
122     protected string CalculateLayoutWithImage(string richText, out IList<UIVertex> verts)
123     {
124         Vector2 extents = rectTransform.rect.size;
125         var settings = GetGenerationSettings(extents);
126 
127         float unitsPerPixel = 1 / pixelsPerUnit;
128 
129         float spaceWidth = cachedTextGenerator.GetPreferredWidth(replaceStr, settings) * unitsPerPixel;
130 
131         float fontSize2 = fontSize * 0.5f;
132 
133         //解析图片标签,并将标签替换为空格
134         imageInfoList.Clear();
135         Match match = null;
136         StringBuilder builder = new StringBuilder();
137         while ((match = imageTagRegex.Match(richText)).Success)
138         {
139             RichTextImageInfo imageInfo = new RichTextImageInfo();
140             imageInfo.name = match.Groups[1].Value;
141             string paras = match.Groups[2].Value;
142             if (!string.IsNullOrEmpty(paras))
143             {
144                 var keyValueCollection = imageParaRegex.Matches(paras);
145                 for (int i = 0; i < keyValueCollection.Count; i++)
146                 {
147                     string key = keyValueCollection[i].Groups[1].Value;
148                     string value = keyValueCollection[i].Groups[2].Value;
149                     imageInfo.SetValue(key, value);
150                 }
151             }
152             imageInfo.size = new Vector2(fontSize2 * imageInfo.widthScale, fontSize2 * imageInfo.heightScale);
153             imageInfo.startVertex = match.Index * 4;
154             int num = Mathf.CeilToInt(imageInfo.size.x / spaceWidth);//占据几个空格
155             imageInfo.vertexLength = num * 4;
156             imageInfoList.Add(imageInfo);
157 
158             builder.Length = 0;
159             builder.Append(richText, 0, match.Index);
160             for (int i = 0; i < num; i++)
161             {
162                 builder.Append(replaceStr);
163             }
164             builder.Append(richText, match.Index + match.Length, richText.Length - match.Index - match.Length);
165             richText = builder.ToString();
166         }
167 
168         // Populate charaters
169         cachedTextGenerator.Populate(richText, settings);
170         verts = cachedTextGenerator.verts;
171         // Last 4 verts are always a new line...
172         int vertCount = verts.Count - 4;
173 
174         //换行处理
175         //0 1|4 5|8  9
176         //3 2|7 6|11 10
177         //例如前两个字为图片标签,第三字为普通文字;那么startVertex为0,vertexLength为8
178         for (int i = 0; i < imageInfoList.Count; i++)
179         {
180             RichTextImageInfo imageInfo = imageInfoList[i];
181             int startVertex = imageInfo.startVertex;
182             int vertexLength = imageInfo.vertexLength;
183             int maxVertex = Mathf.Min(startVertex + vertexLength, vertCount);
184             //如果最边缘顶点超过了显示范围,则将图片移到下一行
185             //之后的图片信息中的起始顶点都往后移
186             if (verts[maxVertex - 2].position.x * unitsPerPixel > rectTransform.rect.xMax)
187             {
188                 richText = richText.Insert(startVertex / 2, "
");
189                 for (int j = i; j < imageInfoList.Count; j++)
190                 {
191                     imageInfoList[j].startVertex += 8;
192                 }
193                 cachedTextGenerator.Populate(richText, settings);
194                 verts = cachedTextGenerator.verts;
195                 vertCount = verts.Count - 4;
196             }
197         }
198 
199         //计算位置
200         for (int i = imageInfoList.Count - 1; i >= 0; i--)
201         {
202             RichTextImageInfo imageInfo = imageInfoList[i];
203             int startVertex = imageInfo.startVertex;
204             if (startVertex < vertCount)
205             {
206                 UIVertex uiVertex = verts[startVertex];
207                 Vector2 pos = uiVertex.position;
208                 pos *= unitsPerPixel;
209                 pos += new Vector2(imageInfo.size.x * 0.5f, fontSize2 * 0.5f);
210                 pos += new Vector2(rectTransform.sizeDelta.x * (rectTransform.pivot.x - 0.5f), rectTransform.sizeDelta.y * (rectTransform.pivot.y - 0.5f));
211                 imageInfo.position = pos;
212                 imageInfo.color = Color.white;
213             }
214             else
215             {
216                 imageInfoList.RemoveAt(i);
217             }
218         }
219 
220         isImageDirty = true;
221 
222         return richText;
223     }
224 
225     private void ApplyUnderlineEffect(RichTextUnderlineTag tag, IList<UIVertex> verts)
226     {
227         float fontSize2 = fontSize * 0.5f;
228         float unitsPerPixel = 1 / pixelsPerUnit;
229 
230         //0 1|4 5|8  9 |12 13
231         //3 2|7 6|11 10|14 15
232         //<material=underline c=#ffffff h=1 n=1 p=2>下划线</material>
233         //以上面为例:
234         //tag.start为42,对应“>” | start对应“下”的左上角顶点
235         //tag.end为44,对应“划”  | end对应“线”下一个字符的左上角顶点
236         //Debug.Log(tag.start);
237         //Debug.Log(tag.end);
238         int start = tag.start * 4;
239         int end = Mathf.Min(tag.end * 4 + 4, verts.Count);
240         UIVertex vt1 = verts[start + 3];
241         UIVertex vt2;
242         float minY = vt1.position.y;
243         float maxY = verts[start].position.y;
244 
245         //换行处理,如需换行,则将一条下划线分割成几条
246         //顶点取样分布,如上图的2,6,10,其中end - 2表示最后一个取样点,即10
247         //对应例子中的下、划、线的右下角顶点
248         for (int i = start + 2; i <= end - 2; i += 4)
249         {
250             vt2 = verts[i];
251             bool newline = Mathf.Abs(vt2.position.y - vt1.position.y) > fontSize2;
252             if (newline || i == end - 2)
253             {
254                 RichTextImageInfo imageInfo = new RichTextImageInfo();
255 
256                 //计算宽高
257                 int tailIndex = !newline && i == end - 2 ? i : i - 4;
258                 vt2 = verts[tailIndex];
259                 minY = Mathf.Min(minY, vt2.position.y);
260                 maxY = Mathf.Max(maxY, verts[tailIndex - 1].position.y);
261                 imageInfo.size = new Vector2((vt2.position.x - vt1.position.x) * unitsPerPixel, tag.height);
262 
263                 //计算位置
264                 Vector2 vertex = new Vector2(vt1.position.x, minY);
265                 vertex *= unitsPerPixel;
266                 vertex += new Vector2(imageInfo.size.x * 0.5f, -tag.height * 0.5f);
267                 vertex += new Vector2(rectTransform.sizeDelta.x * (rectTransform.pivot.x - 0.5f), rectTransform.sizeDelta.y * (rectTransform.pivot.y - 0.5f));
268                 imageInfo.position = vertex;
269 
270                 imageInfo.color = tag.color;
271                 imageInfoList.Add(imageInfo);
272 
273                 vt1 = verts[i + 1];
274                 minY = vt1.position.y;
275                 if (newline && i == end - 2) i -= 4;
276             }
277             else
278             {
279                 minY = Mathf.Min(minY, verts[i].position.y);
280                 maxY = Mathf.Max(maxY, verts[i - 1].position.y);
281             }
282         }
283     }
284 
285     protected void Update()
286     {
287         if (isImageDirty)
288         {
289             isImageDirty = false;
290 
291             //回收当前的图片
292             Image[] images = GetComponentsInChildren<Image>(true);
293             for (int i = 0; i < images.Length; i++)
294             {
295                 RichTextResourceManager.Instance.SetPoolObject(RichTextResourceType.Image, images[i].gameObject);
296             }
297 
298             //生成图片
299             for (int i = 0; i < imageInfoList.Count; i++)
300             {
301                 RichTextImageInfo imageInfo = imageInfoList[i];
302                 var name = imageInfo.name;
303                 var position = imageInfo.position;
304                 var size = imageInfo.size;
305                 var color = imageInfo.color;
306 
307                 GameObject go = RichTextResourceManager.Instance.GetPoolObject(RichTextResourceType.Image);
308                 Image image = go.GetComponent<Image>();
309                 RichTextResourceManager.Instance.SetSprite(name, image);
310                 go.transform.SetParent(rectTransform);
311                 go.transform.localScale = Vector3.one;
312                 image.rectTransform.anchoredPosition = position;
313                 image.rectTransform.sizeDelta = size;
314                 image.color = color;
315             }
316         }
317     }
318 }

效果如下:

原文地址:https://www.cnblogs.com/lyh916/p/9307984.html