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 }
效果如下: