using System; using System.Collections.Generic; using System.Text; using System.Text.RegularExpressions; using UnityEngine; using UnityEngine.Events; using UnityEngine.EventSystems; using UnityEngine.UI; public class UITextHref : Text, IPointerClickHandler { /// /// 图片池 /// readonly List m_ImagesPool = new List(); /// /// 图片的最后一个顶点的索引 /// readonly List m_ImagesVertexIndex = new List(); /// /// 超链接信息列表 /// readonly List m_HrefInfos = new List(); /// /// 解析完最终的文本 /// string m_OutputText; bool clean = false; bool rebuilding = false; bool textDirty = false; /// /// 文本构造器 /// readonly StringBuilder s_TextBuilder = new StringBuilder(); /// /// 正则取出所需要的属性 /// static readonly Regex s_Regex = new Regex(@"", RegexOptions.Singleline); /// /// 超链接正则 /// static readonly Regex s_HrefRegex = new Regex(@"\n\s]+)>(.*?)()", RegexOptions.Singleline); /// /// 超链接点击事件 /// public HrefClickEvent onHrefClick { get { return m_OnHrefClick; } set { m_OnHrefClick = value; } } [Serializable] public class HrefClickEvent : UnityEvent { } [SerializeField] private HrefClickEvent m_OnHrefClick = new HrefClickEvent(); /// /// 点击事件检测是否点击到超链接文本 /// /// public void OnPointerClick(PointerEventData eventData) { Vector2 lp = Vector2.zero; RectTransformUtility.ScreenPointToLocalPointInRectangle( rectTransform, eventData.position, eventData.pressEventCamera, out lp); foreach (var hrefInfo in m_HrefInfos) { var boxes = hrefInfo.boxes; for (var i = 0; i < boxes.Count; ++i) { if (boxes[i].Contains(lp)) { m_OnHrefClick.Invoke(hrefInfo.name); return; } } } } public void SetText(string content) { text = content; if (text.Contains(" /// 清理数据 /// public void Clear() { clean = true; text = ""; for(int idx = rectTransform.childCount- 1;idx >=0;idx--) GameObject.DestroyObject(rectTransform.GetChild(idx).gameObject); m_ImagesPool.Clear(); m_ImagesVertexIndex.Clear(); m_HrefInfos.Clear(); s_TextBuilder.Remove(0, s_TextBuilder.Length); } float checkTime = 0; void LateUpdate() { if(textDirty && !rebuilding) { enabled = false; textDirty = false; enabled = true; } if (!textDirty && !rebuilding && (Time.time - checkTime) > 0.5f && !CheckImage()) { enabled = false; checkTime = Time.time; enabled = true; } } public override void SetVerticesDirty() { base.SetVerticesDirty(); UpdateQuadImage(); } protected override void OnPopulateMesh(VertexHelper toFill) { var orignText = m_Text; m_Text = m_OutputText; base.OnPopulateMesh(toFill); m_Text = orignText; rebuilding = true; UIVertex vert = new UIVertex(); for (var i = 0; i < m_ImagesVertexIndex.Count; i++) { var endIndex = m_ImagesVertexIndex[i]; var rt = m_ImagesPool[i].rectTransform; if (rt == null) continue; var size = rt.sizeDelta; if (endIndex < toFill.currentVertCount) { toFill.PopulateUIVertex(ref vert, endIndex); float anchX = vert.position.x; float anchY = vert.position.y + size.y / 2; rt.anchoredPosition = new Vector2(anchX,anchY); // 抹掉左下角的小黑点 toFill.PopulateUIVertex(ref vert, endIndex - 3); var pos = vert.position; for (int j = endIndex, m = endIndex - 3; j > m; j--) { toFill.PopulateUIVertex(ref vert, endIndex); vert.position = pos; toFill.SetUIVertex(vert, j); } } } if (m_ImagesVertexIndex.Count != 0) { m_ImagesVertexIndex.Clear(); } // 处理超链接包围框 foreach (var hrefInfo in m_HrefInfos) { hrefInfo.boxes.Clear(); if (hrefInfo.startIndex >= toFill.currentVertCount) { continue; } // 将超链接里面的文本顶点索引坐标加入到包围框 toFill.PopulateUIVertex(ref vert, hrefInfo.startIndex); var pos = vert.position; var bounds = new Bounds(pos, Vector3.zero); for (int i = hrefInfo.startIndex, m = hrefInfo.endIndex; i < m; i++) { if (i >= toFill.currentVertCount) { break; } toFill.PopulateUIVertex(ref vert, i); pos = vert.position; if (pos.x < bounds.min.x) // 换行重新添加包围框 { hrefInfo.boxes.Add(new Rect(bounds.min, bounds.size)); bounds = new Bounds(pos, Vector3.zero); } else { bounds.Encapsulate(pos); // 扩展包围框 } } hrefInfo.boxes.Add(new Rect(bounds.min, bounds.size)); } rebuilding = false; } protected void UpdateQuadImage() { #if UNITY_EDITOR if (UnityEditor.PrefabUtility.GetPrefabType(this) == UnityEditor.PrefabType.Prefab) { return; } #endif m_OutputText = GetOutputText(); m_ImagesVertexIndex.Clear(); foreach (Match match in s_Regex.Matches(m_OutputText)) { var picIndex = match.Index; var endIndex = picIndex * 4 + 3; m_ImagesVertexIndex.Add(endIndex); m_ImagesPool.RemoveAll(image => image == null); if (m_ImagesPool.Count == 0) { GetComponentsInChildren(m_ImagesPool); } if (m_ImagesVertexIndex.Count > m_ImagesPool.Count) { var resources = new DefaultControls.Resources(); var go = DefaultControls.CreateImage(resources); go.layer = gameObject.layer; var rt = go.transform as RectTransform; if (rt) { rt.anchorMin = rectTransform.anchorMin; rt.anchorMax = rectTransform.anchorMax; rt.pivot = rectTransform.pivot; rt.SetParent(rectTransform); rt.localPosition = Vector3.zero; rt.localRotation = Quaternion.identity; rt.localScale = Vector3.one; } m_ImagesPool.Add(go.GetComponent()); } var spriteName = match.Groups[1].Value; var size = float.Parse(match.Groups[2].Value); var img = m_ImagesPool[m_ImagesVertexIndex.Count - 1]; if (img.sprite == null || img.sprite.name != spriteName) { //img.sprite = UIUtility.LoadChatFace(spriteName); } img.rectTransform.sizeDelta = new Vector2(size, size); img.enabled = true; } for (var i = m_ImagesVertexIndex.Count; i < m_ImagesPool.Count; i++) { if (m_ImagesPool[i]) { m_ImagesPool[i].enabled = false; } } } /// /// 获取超链接解析后的最后输出文本 /// /// protected string GetOutputText() { s_TextBuilder.Length = 0; m_HrefInfos.Clear(); var indexText = 0; foreach (Match match in s_HrefRegex.Matches(text)) { s_TextBuilder.Append(text.Substring(indexText, match.Index - indexText)); s_TextBuilder.Append(""); // 超链接颜色 var group = match.Groups[1]; var hrefInfo = new HrefInfo { startIndex = s_TextBuilder.Length * 4, // 超链接里的文本起始顶点索引 endIndex = (s_TextBuilder.Length + match.Groups[2].Length - 1) * 4 + 3, name = group.Value }; m_HrefInfos.Add(hrefInfo); s_TextBuilder.Append(match.Groups[2].Value); s_TextBuilder.Append(""); indexText = match.Index + match.Length; } s_TextBuilder.Append(text.Substring(indexText, text.Length - indexText)); return s_TextBuilder.ToString(); } /// /// 超链接信息类 /// private class HrefInfo { public int startIndex; public int endIndex; public string name; public readonly List boxes = new List(); } /// /// 计算文本可能占用的宽度 /// /// /// public float CalcWidth(string content) { string temp = content; float width = 0; foreach (var match in s_Regex.Matches(temp)) { temp = temp.Replace(match.ToString(), "/表情"); } font.RequestCharactersInTexture(temp, font.fontSize, FontStyle.Normal); CharacterInfo characterInfo; for (int idx = 0; idx < temp.Length; idx++) { font.GetCharacterInfo(temp[idx], out characterInfo, font.fontSize); width += characterInfo.advance; } return width; } /// /// 核对图片是否合法 /// /// bool CheckImage() { if (rectTransform.childCount == 0) return true; for (int idx = 0; idx < rectTransform.childCount; idx++) { Transform trans = rectTransform.GetChild(idx); if (trans.localPosition.x < 0) return false; } return true; } }