• [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 }

    效果如下:

  • 相关阅读:
    js数组去重五种方法
    wm_concat 多行字符串拼接
    ORACLE WITH AS 简单用法
    layui laytpl 语法
    看懂Oracle执行计划
    GIT RM -R --CACHED 去掉已经托管在GIT上的文件
    sourceTree使用教程--拉取、获取
    SourceTree忽略文件和文件夹
    layui table 详细讲解
    利用POI实现下拉框级联
  • 原文地址:https://www.cnblogs.com/lyh916/p/9307984.html
Copyright © 2020-2023  润新知