HyperlinkText.cs 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Text;
  5. using System.Text.RegularExpressions;
  6. using UnityEngine;
  7. using UnityEngine.Events;
  8. using UnityEngine.EventSystems;
  9. using UnityEngine.UI;
  10. /// <summary>
  11. /// TextHyperlink 支持Unity超链接富文本语法
  12. /// 例如<a href=https://aws.amazon.com/privacy/>https://aws.amazon.com/privacy/</a>
  13. /// 带颜色的超链接:
  14. /// <a href=https://aws.amazon.com/privacy/><color=#FF0000>https://aws.amazon.com/privacy/</color></a>
  15. /// </summary>
  16. public class HyperlinkText : Text, IPointerClickHandler
  17. {
  18. /// <summary>
  19. /// 超链接信息类
  20. /// </summary>
  21. private class HyperlinkInfo
  22. {
  23. //起始Index
  24. public int StartIndex;
  25. //结束Index
  26. public int EndIndex;
  27. //内容
  28. public string Name;
  29. //包围框
  30. public List<Rect> BoxList = new List<Rect>();
  31. }
  32. #region 私有变量
  33. //超链接正则
  34. private static Regex _hrefRegex = new Regex(@"<a href=([^>\n\s]+)>(.*?)(</a>)", RegexOptions.Singleline);
  35. //颜色正则
  36. private static Regex _colorRegex = new Regex(@"<color=([^>\n\s]+)>(.*?)(</color>)", RegexOptions.Singleline);
  37. //超链接信息列表
  38. private List<HyperlinkInfo> _hyperlinkInfoList = new List<HyperlinkInfo>();
  39. //显示包围盒范围,想用的话输入true
  40. private bool _showTestBound = true;
  41. #endregion
  42. #region 公有变量
  43. #endregion
  44. #region 生命周期
  45. protected override void OnPopulateMesh(VertexHelper toFill)
  46. {
  47. base.OnPopulateMesh(toFill);
  48. InitHyperlinkInfo();
  49. InitHyperlinkBox(toFill);
  50. }
  51. protected override void Start()
  52. {
  53. if (_showTestBound)
  54. {
  55. AddVisibleBound();
  56. }
  57. }
  58. #endregion
  59. #region 公有方法
  60. #endregion
  61. #region 动作
  62. public Action<string> onClickHref;
  63. public void OnPointerClick(PointerEventData eventData)
  64. {
  65. Vector2 localPoint;
  66. RectTransformUtility.ScreenPointToLocalPointInRectangle(
  67. rectTransform, eventData.position, eventData.pressEventCamera, out localPoint);
  68. foreach (HyperlinkInfo hyperlinkInfo in _hyperlinkInfoList)
  69. {
  70. var boxeList = hyperlinkInfo.BoxList;
  71. for (var i = 0; i < boxeList.Count; ++i)
  72. {
  73. if (boxeList[i].Contains(localPoint))
  74. {
  75. //打开网址
  76. //this.Log("TextHyperlink", hyperlinkInfo.Name);
  77. // Application.OpenURL(hyperlinkInfo.Name);
  78. onClickHref?.Invoke(hyperlinkInfo.Name);
  79. return;
  80. }
  81. }
  82. }
  83. }
  84. #endregion
  85. #region 私有方法
  86. /// <summary>
  87. /// 初始化连接信息
  88. /// </summary>
  89. private void InitHyperlinkInfo()
  90. {
  91. //这个值不用,就是个存根。要的是执行后面那个方法
  92. string outputText = GetOutputText(text);
  93. }
  94. /// <summary>
  95. /// 初始化连接包围框
  96. /// </summary>
  97. /// <param name="toFill"></param>
  98. private void InitHyperlinkBox(VertexHelper toFill)
  99. {
  100. UIVertex vert = new UIVertex();
  101. // 处理超链接包围框
  102. foreach (var hrefInfo in _hyperlinkInfoList)
  103. {
  104. hrefInfo.BoxList.Clear();
  105. //一个字符是四个顶点,所以Index要乘以4
  106. int startVertex = hrefInfo.StartIndex * 4;
  107. int endVertex = hrefInfo.EndIndex * 4;
  108. if (startVertex >= toFill.currentVertCount)
  109. {
  110. continue;
  111. }
  112. // 将超链接里面的文本顶点索引坐标加入到包围框
  113. toFill.PopulateUIVertex(ref vert, startVertex);
  114. var pos = vert.position;
  115. var bounds = new Bounds(pos, Vector3.zero);
  116. for (int i = startVertex; i < endVertex; i++)
  117. {
  118. if (i >= toFill.currentVertCount)
  119. {
  120. break;
  121. }
  122. toFill.PopulateUIVertex(ref vert, i);
  123. pos = vert.position;
  124. if (pos.x < bounds.min.x) // 换行重新添加包围框
  125. {
  126. hrefInfo.BoxList.Add(new Rect(bounds.min, bounds.size));
  127. bounds = new Bounds(pos, Vector3.zero);
  128. }
  129. else
  130. {
  131. bounds.Encapsulate(pos); // 扩展包围框
  132. }
  133. }
  134. hrefInfo.BoxList.Add(new Rect(bounds.min, bounds.size));
  135. }
  136. }
  137. /// <summary>
  138. /// 获取超链接解析后的最后输出文本
  139. /// </summary>
  140. /// <returns></returns>
  141. private string GetOutputText(string outputText)
  142. {
  143. StringBuilder stringBuilder = new StringBuilder();
  144. _hyperlinkInfoList.Clear();
  145. int strIndex = 0;
  146. foreach (Match match in _hrefRegex.Matches(outputText))
  147. {
  148. string appendStr = outputText.Substring(strIndex, match.Index - strIndex);
  149. stringBuilder.Append(appendStr);
  150. //空格和回车没有顶点渲染,所以要去掉
  151. stringBuilder = stringBuilder.Replace(" ", "");
  152. stringBuilder = stringBuilder.Replace("\n", "");
  153. int startIndex = stringBuilder.Length;
  154. //第一个是连接url,第二个是连接文本,跳转用url,计算index用文本
  155. Group urlGroup = match.Groups[1];
  156. Group titleGroup = match.Groups[2];
  157. //如果有Color语法嵌套,则还要继续扒,知道吧最终文本扒出来
  158. Match colorMatch = _colorRegex.Match(titleGroup.Value);
  159. if (colorMatch.Groups.Count > 3)
  160. {
  161. titleGroup = colorMatch.Groups[2];
  162. }
  163. stringBuilder.Append(titleGroup.Value);
  164. HyperlinkInfo hyperlinkInfo = new HyperlinkInfo
  165. {
  166. StartIndex = startIndex,
  167. EndIndex = (startIndex + titleGroup.Length),
  168. Name = urlGroup.Value
  169. };
  170. strIndex = match.Index + match.Length;
  171. _hyperlinkInfoList.Add(hyperlinkInfo);
  172. }
  173. stringBuilder.Append(outputText.Substring(strIndex, outputText.Length - strIndex));
  174. return stringBuilder.ToString();
  175. }
  176. /// <summary>
  177. /// 添加可视包围框(测试用方法)
  178. /// </summary>
  179. private void AddVisibleBound()
  180. {
  181. int index = 0;
  182. foreach (var hyperLinkInfo in _hyperlinkInfoList)
  183. {
  184. Color color = new Color(UnityEngine.Random.Range(0f, 1f), UnityEngine.Random.Range(0f, 1f), UnityEngine.Random.Range(0f, 1f), 0.2f);
  185. index++;
  186. foreach (Rect rect in hyperLinkInfo.BoxList)
  187. {
  188. GameObject gameObject = new GameObject();
  189. gameObject.name = string.Format("GOBoundBox[{0}]", hyperLinkInfo.Name);
  190. gameObject.transform.SetParent(this.gameObject.transform);
  191. RectTransform rectTransform = gameObject.AddComponent<RectTransform>();
  192. rectTransform.sizeDelta = rect.size;
  193. rectTransform.localPosition = new Vector3(rect.position.x + rect.size.x / 2, rect.position.y + rect.size.y / 2, 0);
  194. Image image = gameObject.AddComponent<Image>();
  195. image.color = color;
  196. image.raycastTarget = false;
  197. }
  198. }
  199. }
  200. #endregion
  201. }