Unity 超链接 Text
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.UI;
/// <summary>
/// TextHyperlink 支持Unity超链接富文本语法
/// 例如<a href=/privacy/>/privacy/</a>
/// 带颜色的超链接:
/// <a href=/privacy/><color=#FF0000>/privacy/</color></a>
/// </summary>
public class TextHyperlink : Text, IPointerClickHandler
{
/// <summary>
/// 超链接信息类
/// </summary>
private class HyperlinkInfo
{
//起始Index
public int StartIndex;
//结束Index
public int EndIndex;
//内容
public string Name;
//包围框
public List<Rect> BoxList = new List<Rect>();
}
#region 私有变量
//超链接正则
private static Regex _hrefRegex = new Regex(@"<a href=([^>\n\s]+)>(.*?)(</a>)", RegexOptions.Singleline);
//颜色正则
private static Regex _colorRegex = new Regex(@"<color=([^>\n\s]+)>(.*?)(</color>)", RegexOptions.Singleline);
//超链接信息列表
private List<HyperlinkInfo> _hyperlinkInfoList = new List<HyperlinkInfo>();
//显示包围盒范围,想用的话输入true
private bool _showTestBound = true;
#endregion
#region 公有变量
#endregion
#region 生命周期
protected override void OnPopulateMesh(VertexHelper toFill)
{
base.OnPopulateMesh(toFill);
InitHyperlinkInfo();
InitHyperlinkBox(toFill);
}
protected override void Start()
{
//这个代码要在InitHyperlinkInfo();执行后才能执行,如果包围盒显示不出来就需要注意一下时序对不对。
if (_showTestBound)
{
AddVisibleBound();
}
}
#endregion
#region 公有方法
#endregion
#region 动作
public void OnPointerClick(PointerEventData eventData)
{
Vector2 localPoint;
RectTransformUtility.ScreenPointToLocalPointInRectangle(
rectTransform, eventData.position, eventData.pressEventCamera, out localPoint);
foreach (HyperlinkInfo hyperlinkInfo in _hyperlinkInfoList)
{
var boxeList = hyperlinkInfo.BoxList;
for (var i = 0; i < boxeList.Count; ++i)
{
if (boxeList[i].Contains(localPoint))
{
//打开网址
//("TextHyperlink", );
Application.OpenURL(hyperlinkInfo.Name);
return;
}
}
}
}
#endregion
#region 私有方法
/// <summary>
/// 初始化连接信息
/// </summary>
private void InitHyperlinkInfo()
{
//这个值不用,就是个存根。要的是执行后面那个方法
string outputText = GetOutputText(text);
}
/// <summary>
/// 初始化连接包围框
/// </summary>
/// <param name="toFill"></param>
private void InitHyperlinkBox(VertexHelper toFill)
{
UIVertex vert = new UIVertex();
// 处理超链接包围框
foreach (var hrefInfo in _hyperlinkInfoList)
{
hrefInfo.BoxList.Clear();
//一个字符是四个顶点,所以Index要乘以4
int startVertex = hrefInfo.StartIndex * 4;
int endVertex = hrefInfo.EndIndex * 4;
if (startVertex >= toFill.currentVertCount)
{
continue;
}
// 将超链接里面的文本顶点索引坐标加入到包围框
toFill.PopulateUIVertex(ref vert, startVertex);
var pos = vert.position;
var bounds = new Bounds(pos, Vector3.zero);
for (int i = startVertex; i < endVertex; i++)
{
if (i >= toFill.currentVertCount)
{
break;
}
toFill.PopulateUIVertex(ref vert, i);
pos = vert.position;
if (pos.x < bounds.min.x) // 换行重新添加包围框
{
hrefInfo.BoxList.Add(new Rect(bounds.min, bounds.size));
bounds = new Bounds(pos, Vector3.zero);
}
else
{
bounds.Encapsulate(pos); // 扩展包围框
}
}
hrefInfo.BoxList.Add(new Rect(bounds.min, bounds.size));
}
}
/// <summary>
/// 获取超链接解析后的最后输出文本
/// </summary>
/// <returns></returns>
private string GetOutputText(string outputText)
{
StringBuilder stringBuilder = new StringBuilder();
_hyperlinkInfoList.Clear();
int strIndex = 0;
foreach (Match match in _hrefRegex.Matches(outputText))
{
string appendStr = outputText.Substring(strIndex, match.Index - strIndex);
stringBuilder.Append(appendStr);
//空格和回车没有顶点渲染,所以要去掉
stringBuilder = stringBuilder.Replace(" ", "");
stringBuilder = stringBuilder.Replace("\n", "");
int startIndex = stringBuilder.Length;
//第一个是连接url,第二个是连接文本,跳转用url,计算index用文本
Group urlGroup = match.Groups[1];
Group titleGroup = match.Groups[2];
//如果有Color语法嵌套,则还要继续扒,知道吧最终文本扒出来
Match colorMatch = _colorRegex.Match(titleGroup.Value);
if (colorMatch.Groups.Count > 3)
{
titleGroup = colorMatch.Groups[2];
}
stringBuilder.Append(titleGroup.Value);
HyperlinkInfo hyperlinkInfo = new HyperlinkInfo
{
StartIndex = startIndex,
EndIndex = (startIndex + titleGroup.Length),
Name = urlGroup.Value
};
strIndex = match.Index + match.Length;
_hyperlinkInfoList.Add(hyperlinkInfo);
}
stringBuilder.Append(outputText.Substring(strIndex, outputText.Length - strIndex));
return stringBuilder.ToString();
}
/// <summary>
/// 添加可视包围框(测试用方法)
/// </summary>
private void AddVisibleBound()
{
int index = 0;
foreach (var hyperLinkInfo in _hyperlinkInfoList)
{
Color color = new Color(UnityEngine.Random.Range(0f, 1f), UnityEngine.Random.Range(0f, 1f), UnityEngine.Random.Range(0f, 1f), 0.2f);
index++;
foreach (Rect rect in hyperLinkInfo.BoxList)
{
GameObject gameObject = new GameObject();
gameObject.name = string.Format("GOBoundBox[{0}]", hyperLinkInfo.Name);
//();//替换成下面这个方法
gameObject.transform.SetParent(this.gameObject.transform, false);
RectTransform rectTransform = gameObject.AddComponent<RectTransform>();
rectTransform.sizeDelta = rect.size;
rectTransform.localPosition = new Vector3(rect.position.x + rect.size.x / 2, rect.position.y + rect.size.y / 2, 0);
Image image = gameObject.AddComponent<Image>();
image.color = color;
image.raycastTarget = false;
}
}
}
#endregion
}