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

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

 using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Text;
using UnityEngine.EventSystems;
using System;
using UnityEngine;
using UnityEngine.UI; //图片<icon name=*** w=1 h=1 n=*** p=***/>
//下划线<material=underline c=#ffffff h=1 n=*** p=***>blablabla...</material>
public class RichText2 : Text { private FontData fontData = FontData.defaultFontData; //--------------------------------------------------------图片 start
private static readonly string replaceStr = "\u00A0";
private static readonly Regex imageTagRegex = new Regex(@"<icon name=([^>\s]+)([^>]*)/>");//(名字)(属性)
private static readonly Regex imageParaRegex = new Regex(@"(\w+)=([^\s]+)");//(key)=(value)
private List<RichTextImageInfo> imageInfoList = new List<RichTextImageInfo>();
private bool isImageDirty = false;
//--------------------------------------------------------图片 end //--------------------------------------------------------文字 start
private RichTextParser richTextParser = new RichTextParser();
//--------------------------------------------------------文字 end //--------------------------------------------------------事件 start //--------------------------------------------------------事件 end protected RichText2()
{
fontData = typeof(Text).GetField("m_FontData", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(this) as FontData;
} readonly UIVertex[] m_TempVerts = new UIVertex[];
protected override void OnPopulateMesh(VertexHelper toFill)
{
if (font == null)
return; // We don't care if we the font Texture changes while we are doing our Update.
// The end result of cachedTextGenerator will be valid for this instance.
// Otherwise we can get issues like Case 619238.
m_DisableFontTextureRebuiltCallback = true; //处理图片标签
string richText = text;
IList<UIVertex> verts = null;
richText = CalculateLayoutWithImage(richText, out verts); //处理文字标签
List<RichTextTag> tagList = null;
richTextParser.Parse(richText, out tagList);
for (int i = ; i < tagList.Count; i++)
{
RichTextTag tag = tagList[i];
switch (tag.tagType)
{
case RichTextTagType.None:
break;
case RichTextTagType.Underline:
ApplyUnderlineEffect(tag as RichTextUnderlineTag, verts);
break;
default:
break;
}
} Vector2 extents = rectTransform.rect.size; var settings = GetGenerationSettings(extents);
cachedTextGenerator.Populate(text, settings); Rect inputRect = rectTransform.rect; // get the text alignment anchor point for the text in local space
Vector2 textAnchorPivot = GetTextAnchorPivot(fontData.alignment);
Vector2 refPoint = Vector2.zero;
refPoint.x = Mathf.Lerp(inputRect.xMin, inputRect.xMax, textAnchorPivot.x);
refPoint.y = Mathf.Lerp(inputRect.yMin, inputRect.yMax, textAnchorPivot.y); // Determine fraction of pixel to offset text mesh.
Vector2 roundingOffset = PixelAdjustPoint(refPoint) - refPoint; // Apply the offset to the vertices
//IList<UIVertex> verts = cachedTextGenerator.verts;
float unitsPerPixel = / pixelsPerUnit;
//Last 4 verts are always a new line...
int vertCount = verts.Count - ; toFill.Clear();
if (roundingOffset != Vector2.zero)
{
for (int i = ; i < vertCount; ++i)
{
int tempVertsIndex = i & ;
m_TempVerts[tempVertsIndex] = verts[i];
m_TempVerts[tempVertsIndex].position *= unitsPerPixel;
m_TempVerts[tempVertsIndex].position.x += roundingOffset.x;
m_TempVerts[tempVertsIndex].position.y += roundingOffset.y;
if (tempVertsIndex == )
toFill.AddUIVertexQuad(m_TempVerts);
}
}
else
{
//Debug.Log(unitsPerPixel);
for (int i = ; i < vertCount; ++i)
{
int tempVertsIndex = i & ;
m_TempVerts[tempVertsIndex] = verts[i];
m_TempVerts[tempVertsIndex].position *= unitsPerPixel;
if (tempVertsIndex == )
toFill.AddUIVertexQuad(m_TempVerts);
//Debug.LogWarning(i + "_" + tempVertsIndex + "_" + m_TempVerts[tempVertsIndex].position);
}
}
m_DisableFontTextureRebuiltCallback = false;
} protected string CalculateLayoutWithImage(string richText, out IList<UIVertex> verts)
{
Vector2 extents = rectTransform.rect.size;
var settings = GetGenerationSettings(extents); float unitsPerPixel = / pixelsPerUnit; float spaceWidth = cachedTextGenerator.GetPreferredWidth(replaceStr, settings) * unitsPerPixel; float fontSize2 = fontSize * 0.5f; //解析图片标签,并将标签替换为空格
imageInfoList.Clear();
Match match = null;
StringBuilder builder = new StringBuilder();
while ((match = imageTagRegex.Match(richText)).Success)
{
RichTextImageInfo imageInfo = new RichTextImageInfo();
imageInfo.name = match.Groups[].Value;
string paras = match.Groups[].Value;
if (!string.IsNullOrEmpty(paras))
{
var keyValueCollection = imageParaRegex.Matches(paras);
for (int i = ; i < keyValueCollection.Count; i++)
{
string key = keyValueCollection[i].Groups[].Value;
string value = keyValueCollection[i].Groups[].Value;
imageInfo.SetValue(key, value);
}
}
imageInfo.size = new Vector2(fontSize2 * imageInfo.widthScale, fontSize2 * imageInfo.heightScale);
imageInfo.startVertex = match.Index * ;
int num = Mathf.CeilToInt(imageInfo.size.x / spaceWidth);//占据几个空格
imageInfo.vertexLength = num * ;
imageInfoList.Add(imageInfo); builder.Length = ;
builder.Append(richText, , match.Index);
for (int i = ; i < num; i++)
{
builder.Append(replaceStr);
}
builder.Append(richText, match.Index + match.Length, richText.Length - match.Index - match.Length);
richText = builder.ToString();
} // Populate charaters
cachedTextGenerator.Populate(richText, settings);
verts = cachedTextGenerator.verts;
// Last 4 verts are always a new line...
int vertCount = verts.Count - ; //换行处理
//0 1|4 5|8 9
//3 2|7 6|11 10
//例如前两个字为图片标签,第三字为普通文字;那么startVertex为0,vertexLength为8
for (int i = ; i < imageInfoList.Count; i++)
{
RichTextImageInfo imageInfo = imageInfoList[i];
int startVertex = imageInfo.startVertex;
int vertexLength = imageInfo.vertexLength;
int maxVertex = Mathf.Min(startVertex + vertexLength, vertCount);
//如果最边缘顶点超过了显示范围,则将图片移到下一行
//之后的图片信息中的起始顶点都往后移
if (verts[maxVertex - ].position.x * unitsPerPixel > rectTransform.rect.xMax)
{
richText = richText.Insert(startVertex / , "\r\n");
for (int j = i; j < imageInfoList.Count; j++)
{
imageInfoList[j].startVertex += ;
}
cachedTextGenerator.Populate(richText, settings);
verts = cachedTextGenerator.verts;
vertCount = verts.Count - ;
}
} //计算位置
for (int i = imageInfoList.Count - ; i >= ; i--)
{
RichTextImageInfo imageInfo = imageInfoList[i];
int startVertex = imageInfo.startVertex;
if (startVertex < vertCount)
{
UIVertex uiVertex = verts[startVertex];
Vector2 pos = uiVertex.position;
pos *= unitsPerPixel;
pos += new Vector2(imageInfo.size.x * 0.5f, fontSize2 * 0.5f);
pos += new Vector2(rectTransform.sizeDelta.x * (rectTransform.pivot.x - 0.5f), rectTransform.sizeDelta.y * (rectTransform.pivot.y - 0.5f));
imageInfo.position = pos;
imageInfo.color = Color.white;
}
else
{
imageInfoList.RemoveAt(i);
}
} isImageDirty = true; return richText;
} private void ApplyUnderlineEffect(RichTextUnderlineTag tag, IList<UIVertex> verts)
{
float fontSize2 = fontSize * 0.5f;
float unitsPerPixel = / pixelsPerUnit; //0 1|4 5|8 9 |12 13
//3 2|7 6|11 10|14 15
//<material=underline c=#ffffff h=1 n=1 p=2>下划线</material>
//以上面为例:
//tag.start为42,对应“>” | start对应“下”的左上角顶点
//tag.end为44,对应“划” | end对应“线”下一个字符的左上角顶点
//Debug.Log(tag.start);
//Debug.Log(tag.end);
int start = tag.start * ;
int end = Mathf.Min(tag.end * + , verts.Count);
UIVertex vt1 = verts[start + ];
UIVertex vt2;
float minY = vt1.position.y;
float maxY = verts[start].position.y; //换行处理,如需换行,则将一条下划线分割成几条
//顶点取样分布,如上图的2,6,10,其中end - 2表示最后一个取样点,即10
//对应例子中的下、划、线的右下角顶点
for (int i = start + ; i <= end - ; i += )
{
vt2 = verts[i];
bool newline = Mathf.Abs(vt2.position.y - vt1.position.y) > fontSize2;
if (newline || i == end - )
{
RichTextImageInfo imageInfo = new RichTextImageInfo(); //计算宽高
int tailIndex = !newline && i == end - ? i : i - ;
vt2 = verts[tailIndex];
minY = Mathf.Min(minY, vt2.position.y);
maxY = Mathf.Max(maxY, verts[tailIndex - ].position.y);
imageInfo.size = new Vector2((vt2.position.x - vt1.position.x) * unitsPerPixel, tag.height); //计算位置
Vector2 vertex = new Vector2(vt1.position.x, minY);
vertex *= unitsPerPixel;
vertex += new Vector2(imageInfo.size.x * 0.5f, -tag.height * 0.5f);
vertex += new Vector2(rectTransform.sizeDelta.x * (rectTransform.pivot.x - 0.5f), rectTransform.sizeDelta.y * (rectTransform.pivot.y - 0.5f));
imageInfo.position = vertex; imageInfo.color = tag.color;
imageInfoList.Add(imageInfo); vt1 = verts[i + ];
minY = vt1.position.y;
if (newline && i == end - ) i -= ;
}
else
{
minY = Mathf.Min(minY, verts[i].position.y);
maxY = Mathf.Max(maxY, verts[i - ].position.y);
}
}
} protected void Update()
{
if (isImageDirty)
{
isImageDirty = false; //回收当前的图片
Image[] images = GetComponentsInChildren<Image>(true);
for (int i = ; i < images.Length; i++)
{
RichTextResourceManager.Instance.SetPoolObject(RichTextResourceType.Image, images[i].gameObject);
} //生成图片
for (int i = ; i < imageInfoList.Count; i++)
{
RichTextImageInfo imageInfo = imageInfoList[i];
var name = imageInfo.name;
var position = imageInfo.position;
var size = imageInfo.size;
var color = imageInfo.color; GameObject go = RichTextResourceManager.Instance.GetPoolObject(RichTextResourceType.Image);
Image image = go.GetComponent<Image>();
RichTextResourceManager.Instance.SetSprite(name, image);
go.transform.SetParent(rectTransform);
go.transform.localScale = Vector3.one;
image.rectTransform.anchoredPosition = position;
image.rectTransform.sizeDelta = size;
image.color = color;
}
}
}
}

效果如下:

[UGUI]图文混排(五):添加下划线的更多相关文章

  1. Unity UGUI图文混排(五) -- 一张图集对应多个Text

    继上一篇说的更新了一张图集对应多个Text的功能,为了节省资源嘛 这里,但是也没有舍弃之前的一个Text一个图集,因为我感觉应该两个都有用,于是我重新写了一个脚本 1.其实大体跟前面的都没变,解析标签 ...

  2. Unity琐碎(3) UGUI 图文混排解决方案和优化

    感觉使用Unity之后总能看到各种各样解决混排的方案,只能说明Unity不够体恤下情啊.这篇文章主要讲一下个人在使用过程中方案选择和优化过程,已做记录.顺便提下,开源很多意味着坑,还是要开实际需求. ...

  3. Unity UGUI图文混排源码(二)

    Unity UGUI图文混排源码(一):http://blog.csdn.net/qq992817263/article/details/51112304 Unity UGUI图文混排源码(二):ht ...

  4. Unity UGUI图文混排源码(三) -- 动态表情

    这里是根据图文混排源码(二)进一步修改的,其他链接也不贴了,就贴一个链接就好了,第一次看这文章的同学可以先去看看其他几篇文章 Unity UGUI图文混排源码(二):http://blog.csdn. ...

  5. Unity UGUI图文混排源码(一)

    Unity UGUI图文混排源码(一):http://blog.csdn.net/qq992817263/article/details/51112304 Unity UGUI图文混排源码(二):ht ...

  6. Unity UGUI图文混排(七) -- 下划线

    之前更新超链接的时候,忘了搭配实现一个下划线的功能,这篇文章就是来补上这一个功能,时间有点长,一方面没有很好的思路,一方面也没多少时间. 先在网上收集了一下下划线的实现操作,一种是在文本下再创建一个文 ...

  7. [UGUI]图文混排(二):Text源码分析

    UGUI源码: https://bitbucket.org/Unity-Technologies/ui/downloads/?tab=tags 首先下载一份UGUI源码,这里我下载的版本是5.3.2f ...

  8. [UGUI]图文混排(一):标签制定和解析

    参考链接: https://github.com/SylarLi/RichText/tree/master/Assets/Scripts 正则表达式: https://blog.csdn.net/ly ...

  9. Unity UGUI图文混排(六) -- 超链接

    图文混排更新到超链接这儿,好像也差不多了,不过就在最后一点,博主也表现得相当不专业,直接整合了山中双木林同学提供的超链接的解决方案,博主甚至没来得及细看就直接复制了,但感觉还是挺好用的. 博主已经将超 ...

随机推荐

  1. redis连接错误处理方案分享

    今天为了搞压测,定位是不是redis瓶颈. 在我们的服务器10.90.2.101上安装了一个redis,版本(redis-3.2.8.tar.gz),没有做任何配置,直接make & make ...

  2. RichEdit文字背景色的处理

    uses RichEdit; procedure RichEditSetBackColor( // 设置RichEdit文字背景色 mRichEdit: TRichEdit; // Rich编辑框 m ...

  3. Jenkins的详细安装

    操作环境:Windows 一.环境准备 1 安装JDK 本文采用jdk-8u111-windows-x64.exe: 2 配置tomcat 本文采用tomcat8,无需安装,配置JAVA_HOME及J ...

  4. 【linux】之常用命令-杂项

    查看端口:lsof -i :80 二.内存大小[root@xbidc ~]# cat /proc/meminfo |grep MemTotalMemTotal: 1034612 kB[root@xbi ...

  5. Redis持久化实践及灾难恢复模拟 [转]

    参考资料:Redis Persistence http://redis.io/topics/persistenceGoogle Groups https://groups.google.com/for ...

  6. SpringBoot工程+热部署进行远程调试

    本文转载自:https://blog.csdn.net/qq_31868349/article/details/78553901 SpringBoot工程+热部署进行远程调试 本地端添加配置 在pom ...

  7. 服务注册发现consul之三:服务发现比较:Consul vs Zookeeper vs Etcd vs Eureka

    这里就平时经常用到的服务发现的产品进行下特性的对比,首先看下结论: Feature Consul zookeeper etcd euerka 服务健康检查 服务状态,内存,硬盘等 (弱)长连接,kee ...

  8. bundle adjustment 玩具程序

    结合 bundle adjustment原理(1) 和 Levenberg-Marquardt 的 MATLAB 代码 两篇博客的成果,调用MATLAB R2016a中 bundleAdjustmen ...

  9. jQuery的杂项

    <script  src="引入插件"></script> 位置应写在head中,在http协议中,当你浏览网页时,会先加载head里面的东西  刷新页面时 ...

  10. vultr上 windows使用pptp拨号来实现冗余双网关的解决方案

    rasdial是拨号程序,pptpvpn是网卡拨号名称,后面跟的是帐号和密码.pptpvpn见下图:就是提前创建好一个PPTP的拨号连接 上面是启动时候的计划任务,那么万一拨号中断,要继续重拨还需要做 ...