unity ugui text 超链接和下划线,支持部分富文本格式

unity版本:2021.3.6f1
局限性:
1.测试发现不能使用 size 富文本标签,
2.同一文本不能设置不同颜色的超链接文本
其它:代码中注释掉使用innerTextColor的地方,可以使用富文本设置超链接颜色, 但是下划线是文本本身颜色

项目需要用到该功能, 搜索和参考了很多文章,要么不支持富文本,要不没有下划线,要么是错误的,修修改改后满足我的需求,代码如下

using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;namespace MyTool.Tools
{/// <summary>/// 文本控件支持超链接、下划线/// </summary>public class HyperlinkText : Text, IPointerClickHandler{public Action<string> onHyperlinkClick;/// 超链接信息类private class HyperlinkInfo{public int startIndex;public int endIndex;public string name;public readonly List<Rect> boxes = new List<Rect>();public List<int> linefeedIndexList = new List<int>();}/// 解析完最终的文本private string m_OutputText;/// 超链接信息列表private readonly List<HyperlinkInfo> m_HrefInfos = new List<HyperlinkInfo>();/// 文本构造器protected StringBuilder s_TextBuilder = new StringBuilder();[Tooltip("超链接文本颜色")][SerializeField] private Color32 innerTextColor = new Color32(36, 64, 180, 255);/// 超链接正则private static readonly Regex s_HrefRegex = new Regex(@"<href=([^>\n\s]+)>(.*?)(</href>)", RegexOptions.Singleline);// ugui富文本标签// 格式1:<b></b>  <i></i>private static readonly string[] _uguiSymbols1 = { "b", "i" };// 格式2:<color=#ffffff></color> <color=red></color>private static readonly string[] _uguiSymbols2 = { "color", "size" };public string GetHyperlinkInfo { get { return text; } }public override void SetVerticesDirty(){base.SetVerticesDirty();text = GetHyperlinkInfo;m_OutputText = GetOutputText(text);}protected override void OnPopulateMesh(VertexHelper toFill){var orignText = m_Text;m_Text = m_OutputText;base.OnPopulateMesh(toFill);m_Text = orignText;UIVertex vert = new UIVertex();// 处理超链接包围框foreach (var hrefInfo in m_HrefInfos){hrefInfo.boxes.Clear();hrefInfo.linefeedIndexList.Clear();if (hrefInfo.startIndex >= toFill.currentVertCount)continue;// 将超链接里面的文本顶点索引坐标加入到包围框toFill.PopulateUIVertex(ref vert, hrefInfo.startIndex);var pos = vert.position;var bounds = new Bounds(pos, Vector3.zero);hrefInfo.linefeedIndexList.Add(hrefInfo.startIndex);for (int i = hrefInfo.startIndex, m = hrefInfo.endIndex; i < m; i++){if (i >= toFill.currentVertCount)break;toFill.PopulateUIVertex(ref vert, i);vert.color = innerTextColor;toFill.SetUIVertex(vert, i);pos = vert.position;bool needEncapsulate = true;if (i > 4 && (i - hrefInfo.startIndex) % 4 == 0){UIVertex lastV = new UIVertex();toFill.PopulateUIVertex(ref lastV, i - 4);var lastPos = lastV.position;if (pos.x < lastPos.x && pos.y < lastPos.y) // 换行重新添加包围框{hrefInfo.boxes.Add(new Rect(bounds.min, bounds.size));hrefInfo.linefeedIndexList.Add(i);bounds = new Bounds(pos, Vector3.zero);needEncapsulate = false;}}if (needEncapsulate){bounds.Encapsulate(pos); // 扩展包围框}}hrefInfo.boxes.Add(new Rect(bounds.min, bounds.size));}//一个字一个字的划 效率差 而且字与字之间容易有接缝DrawUnderLine(toFill);}private void DrawUnderLine(VertexHelper vh){UIVertex vert = new UIVertex();List<Vector3> startPosList = new List<Vector3>();List<Vector3> endPosList = new List<Vector3>();foreach (var hrefInfo in m_HrefInfos){if (hrefInfo.startIndex >= vh.currentVertCount) continue;float minY = float.MaxValue;for (int i = hrefInfo.startIndex, m = hrefInfo.endIndex; i < m; i += 4){if (i >= vh.currentVertCount)break;if (hrefInfo.linefeedIndexList.Contains(i)){for (int j = 0; j < startPosList.Count; j++){MeshUnderLine(vh, new Vector2(startPosList[j].x, minY), new Vector2(endPosList[j].x, minY));}startPosList.Clear();endPosList.Clear();}vh.PopulateUIVertex(ref vert, i + 3);startPosList.Add(vert.position);vh.PopulateUIVertex(ref vert, i + 2);endPosList.Add(vert.position);if (vert.position.y < minY){minY = vert.position.y;}}for (int j = 0; j < startPosList.Count; j++){MeshUnderLine(vh, new Vector2(startPosList[j].x, minY), new Vector2(endPosList[j].x, minY));}startPosList.Clear();endPosList.Clear();}}private void MeshUnderLine(VertexHelper vh, Vector2 startPos, Vector2 endPos){Vector2 extents = rectTransform.rect.size;var setting = GetGenerationSettings(extents);TextGenerator underlineText = new TextGenerator();underlineText.Populate("—", setting);IList<UIVertex> lineVer = underlineText.verts;/*new UIVertex[4];*///"_"的的顶点数组Vector3[] pos = new Vector3[4];pos[0] = startPos + new Vector2(-1f, 0);pos[3] = startPos + new Vector2(-1f, 4f);pos[2] = endPos + new Vector2(1f, 4f);pos[1] = endPos + new Vector2(1f, 0);UIVertex[] tempVerts = new UIVertex[4];for (int i = 0; i < 4; i++){tempVerts[i] = lineVer[i];tempVerts[i].color = innerTextColor;tempVerts[i].position = pos[i];}vh.AddUIVertexQuad(tempVerts);}/// <summary>/// 获取超链接解析后的最后输出文本/// </summary>/// <returns></returns>protected virtual string GetOutputText(string outputText){s_TextBuilder.Length = 0;m_HrefInfos.Clear();var indexText = 0;int count = 0;foreach (Match match in s_HrefRegex.Matches(outputText)){string appendStr = outputText.Substring(indexText, match.Index - indexText);s_TextBuilder.Append(appendStr);//空格和回车没有顶点渲染,所以要去掉count += appendStr.Length - appendStr.Replace(" ", "").Replace("\n", "").Length;//去掉富文本标签的长度for (int i = 0; i < _uguiSymbols1.Length; i++){count += appendStr.Length - appendStr.Replace($"<{_uguiSymbols1[i]}>", "").Replace($"</{_uguiSymbols1[i]}>", "").Length;}for (int i = 0; i < _uguiSymbols2.Length; i++){string pattern = $"<{_uguiSymbols2[i]}=(.*?)>";count += appendStr.Length - Regex.Replace(appendStr, pattern, "").Length;count += appendStr.Length - appendStr.Replace($"</{_uguiSymbols2[i]}>", "").Length;}int startIndex = (s_TextBuilder.Length - count) * 4;var group = match.Groups[1];var hrefInfo = new HyperlinkInfo{startIndex = startIndex, // 超链接里的文本起始顶点索引endIndex = startIndex + (match.Groups[2].Length * 4),name = group.Value};m_HrefInfos.Add(hrefInfo);s_TextBuilder.Append(match.Groups[2].Value);indexText = match.Index + match.Length;}s_TextBuilder.Append(outputText.Substring(indexText, outputText.Length - indexText));return s_TextBuilder.ToString();}/// <summary>/// 点击事件检测是否点击到超链接文本/// </summary>/// <param name="eventData"></param>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)){if (onHyperlinkClick != null)onHyperlinkClick.Invoke(hrefInfo.name);return;}}}}#if UNITY_EDITOR//需延迟调用该方法private void AddVisibleBound(){int index = 0;foreach (var hrefInfo in m_HrefInfos){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 hrefInfo.boxes){GameObject gameObject = new GameObject();gameObject.name = string.Format("GOBoundBox[{0}]", hrefInfo.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;}}}
#endif}
}

编辑器扩展
在这里插入图片描述
代码

using UnityEditor;
using UnityEngine;namespace MyTool
{[CanEditMultipleObjects][CustomEditor(typeof(Tools.HyperlinkText), true)]public class HyperlinkTextEditor : UnityEditor.UI.TextEditor{SerializedProperty _innerTextColor;protected override void OnEnable(){base.OnEnable();_innerTextColor = serializedObject.FindProperty("innerTextColor");}public override void OnInspectorGUI(){base.OnInspectorGUI();serializedObject.Update();EditorGUILayout.PropertyField(_innerTextColor, new GUIContent("Inner Text Color"));serializedObject.ApplyModifiedProperties();if (GUI.changed){EditorUtility.SetDirty(target);}}}
}

Demo代码

using MyTool.Tools;
using System.Collections;
using UnityEngine;public class Demo2 : MonoBehaviour
{public HyperlinkText text;// Start is called before the first frame updatevoid Start(){//设置点击回调text.onHyperlinkClick = OnClickText;StartCoroutine(AddVisibleBound());}IEnumerator AddVisibleBound(){yield return null;text.AddVisibleBound();}void OnClickText(string s){if (s == "第一段第一句"){Debug.Log($"111---{s}");}else if (s == "第二段第一句"){Debug.Log($"222---{s}");}else{Debug.Log($"333---{s}");}}
}

demo测试文本

<color=#ffffff><b><size=36>背着手踱着。</size></b></color><color=ffffff><href=第一段第一句>路上只我一个人</href></color><size=45>这一片天地好像是我的;我也像超出了平常旳自己,到了另一世界里。我爱热闹,也爱冷静;爱群居,也爱独处。像今晚上,一个人在这苍茫旳月下,什么都可以想,什么都可以不想,便觉是个自由的人。</size>白天里一定要做的事,一定要说的话,现在都可不理。<b><color=green>这是独处的妙处,我且受用这无边的荷香月色好了。</color></b><href=第二段第一句>曲曲折折的荷塘上面</href>,弥望旳是田田的叶子。叶子出水很高,像亭亭旳舞女旳裙。层层的叶子中间,零星地点缀着些白花,有袅娜(niǎo,nuó)地开着旳,有羞涩地打着朵儿旳;正如一粒粒的明珠,又如碧天里的星星,又如刚出浴的美人。微风过处,送来缕缕清香,仿佛远处高楼上渺茫的歌声似的。这时候叶子与花也有一丝的颤动,像闪电般,霎时传过荷塘的那边去了。叶子本是肩并肩密密地挨着,这便宛然有了一道凝碧的波痕。叶子底下是脉脉()的流水,遮住了,不能见一些颜色;而叶子却更见风致了。<size=60>月光如流水一般,静静地泻在这一片叶子和花上。薄薄的青雾浮起在荷塘里。叶子和花仿佛在牛乳中洗过一样;又像笼着轻纱的梦。虽然是满月,天上却有一层淡淡的云,所以不能朗照;但我以为</size>这恰是到了好处——酣眠固不可少,小睡也别有风味的。月光是隔了树照过来的,高处丛生的灌木,落下参差的斑驳的黑影,峭楞楞如鬼一般;弯弯的杨柳的稀疏的倩影,却又像是画在荷叶上。塘中的月色并不均匀;但光与影有着和谐的旋律,<href=第三段>如梵婀(ē)(英语violin小提琴的译音)上奏着的名曲</href><color=red><i>荷塘的四面,远远近近,高高低低都是树,而杨柳最多。</i></color><href=第四段>这些树将一片荷塘重重围住</href>;只在小路一旁,漏着几段空隙,像是特为月光留下的。树色一例是阴阴的,乍看像一团烟雾;但杨柳的丰姿,便在烟雾里也辨得出。树梢上隐隐约约的是一带远山,只有些大意罢了。树缝里也漏着一两点路灯光,没精打采的,是渴睡人的眼。这时候最热闹的,要数树上的蝉声与水里的蛙声;但热闹是它们的,我什么也没有。

ui
在这里插入图片描述
效果
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/105965.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

windows部署django服务器

windows部署django服务器 1、安装IIS1.1 控制面板-----程序----程序和功能----启用或关闭windows功能1.2安装IIS服务器&#xff0c;完成后&#xff0c;重新进入&#xff0c;把CGI安装进系统 2、安装python与虚拟环境2.1 安装python2.2 安装virtualenv虚拟环境2.3 创建一个虚拟环…

求二叉树的高度——函数递归的思想

二叉树的高度&#xff1a;左右两个数最高的那个的1 int TreeHight(BTNode* root) {if (root NULL){return 0;}int lefhightTreeHight(root->left);int righthight TreeHight(root->right);return lefhight > righthight ? TreeHight(root->left) 1 : TreeHight…

想要精通算法和SQL的成长之路 - 连续的子数组和

想要精通算法和SQL的成长之路 - 连续的子数组和 前言一. 连续的子数组和1.1 最原始的前缀和1.2 前缀和 哈希表 前言 想要精通算法和SQL的成长之路 - 系列导航 一. 连续的子数组和 原题链接 1.1 最原始的前缀和 如果这道题目&#xff0c;用前缀和来算&#xff0c;我们的思路…

雷电模拟器上使用第一个frida(三)简单的使用实例

经过前两篇 雷电模拟器上使用第一个frida&#xff08;一&#xff09;之安装-CSDN博客雷电模拟器上使用第一个frida&#xff08;二&#xff09;之su超级权限-CSDN博客 本篇开始记录如何使用frida进行hook。 一、先让手机模拟器端的frida server运行起来 虽然是让手机模拟器端…

Rust 流程控制

开发中最常见的用来控制执行流的结构是判断和循环。 判断 Rust 中的 if 表达式允许根据条件执行不同的代码分支&#xff0c;提供一个条件并表示 “如果条件满足&#xff0c;运行这段代码&#xff1b;如果条件不满足&#xff0c;不运行这段代码。” 需要注意的是&#xff0c;…

华为云云耀云服务器L实例评测|企业项目最佳实践之计划任务与Queue队列实践 (十)

十一、计划任务与Queue队列实践&#xff1a; 1. 计划任务&#xff1a; Linux环境下定时或者周期性的执行一些任务通常由cron这个守护进程来完成&#xff0c;这是一个系统自带的相对也比较方便的系统工具。 sudo apt-get install cron // 默认自带目录结构&#xff1a; 目录说…

驾驶数字未来:汽车业界数字孪生技术的崭新前景

随着数字化时代的到来&#xff0c;汽车行业正经历着前所未有的变革。数字孪生技术&#xff0c;作为一种前沿的数字化工具&#xff0c;正在为汽车行业带来革命性的影响&#xff0c;不仅改变着汽车制造和维护的方式&#xff0c;也为消费者带来了前所未有的体验。让我们一起探讨&a…

算法通过村第十四关-堆|白银笔记|经典问题

文章目录 前言在数组中寻找第K大的元素堆排序原理合并K个排序链表总结 前言 提示&#xff1a;想要从讨厌的地方飞出来&#xff0c;就得有藏起来的翅膀。 --三岛由纪夫《萨德侯爵夫人》 这里我们主要看一下经典的题目&#xff0c;这三个题目来说都是堆的热点问题。重点再理解处理…

SpringMVC之WEB-INF下页面跳转@ModelAttributeIDEA tomcat控制台中文乱码问题处理

WEB-INF下页面跳转 ModelAttribute来注解非请求处理方法 用途&#xff1a;预加载数据&#xff0c;会在每个RequestMapping方法执行之前调用。 特点&#xff1a;无需返回视图&#xff0c;返回类型void IDEA tomcat控制台中文乱码问题处理 复制此段代码&#xff1a;-Dfile.e…

有 AI,无障碍,AIoT 设备为视障人群提供便利

据世界卫生组织统计&#xff0c;全球共 22 亿人视力受损&#xff0c;包含 2.85 亿视障人群和 3,900 万全盲人群。而且&#xff0c;这一数字将随老龄化加剧不断增加。 虽然视障人群面临着诸多不便&#xff0c;但是针对视障人群的辅助设备却存在成本高、维护困难、操作复杂等问题…

【传输层协议】UDP/TCP结构特点与原理(详解)

文章目录 1. UDP1.1 UDP结构1.2 UDP特点1. 无连接2. 不可靠3. 面向数据报4. 缓冲区5. 大小受限6. 无序性 2. TCP2.1 TCP结构2.2 TCP特点1. 有连接2. 可靠性3. 面向字节流4. 拥塞控制5. 头部开销 2.3 TCP原理1. 确认应答&#xff08;安全机制&#xff09;2. 超时重传&#xff08…

爬虫 | 基础模块了解

文章目录 &#x1f4da;http协议&#x1f4da;requests模块&#x1f4da;re模块&#x1f407; re.I 或 re.IGNORECASE&#x1f407;re.M或 re.MULTILINE&#x1f407;re.S 或 re.DOTALL&#x1f407; re.A 或 re.ASCII&#x1f407; re.X 或 re.VERBOSE&#x1f407;特殊字符类…

攻防世界数据逆向 2023

https://adworld.xctf.org.cn/contest/list?rwNmOdr1697354606875 目录 请求数据参数加密 cookie加密 响应数据解密 代码 请求数据参数加密 我们可以根据请求的关键字qmze1yzvhyzcyyjr获取到对应的加密地方 可以看到使用了函数_0x1dc70进行了加密 cookie加密 该步骤需…

SpringBoot面试题4:Spring Boot 支持哪些日志框架?推荐和默认的日志框架是哪个?

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:Spring Boot 支持哪些日志框架?推荐和默认的日志框架是哪个? Spring Boot支持多种日志框架,包括以下几种: Logback:Logback 是一个快速、灵活…

面试总结(mysql定精度/oom排查/spring三级缓存/stream流)

Mysql数据类型上的一个把握 1、MySQL Decimal为什么不会丢失精度 DECIMAL的存储方式和其他数据类型都不同&#xff0c;它是以字符串形式存储的。假设一个字段为DECIMAL(3,0)&#xff0c;当我们存入100时&#xff0c;实际上存入的1、0、0这三个字符拼接而成的字符串的二进制值&…

物联网AI MicroPython传感器学习 之 TDS水质检测传感器

学物联网&#xff0c;来万物简单IoT物联网&#xff01;&#xff01; 一、产品简介 TDS是Total Dissolved Solids的缩写&#xff0c;中文名总溶解固体&#xff0c;也叫溶解性固体总量&#xff0c;他标识1升水中溶有多少毫克的可溶解性固体。一般来说&#xff0c;TDS值越高&…

react中利用useRef、forwardRef、useImperativeHandle获取并处理dom

React如何给组件设置ref属性&#xff0c;如果直接绑给组件&#xff0c;代码如下&#xff1a; import { useRef } from "react"function MyInput() {return (<input type"text"/>) }function App() {const myRef useRef(null)const handleClick ()…

电脑上播放4K视频需要具备哪些条件?

在电视上播放 4K&#xff08; 4096 2160 像素&#xff09;视频是很简单的&#xff0c;但在电脑设备上播放 4K 视频并不容易。相反&#xff0c;它们有自己必须满足的硬件要求。 如果不满足要求&#xff0c;在电脑上打开 4K 分辨率文件或大型视频文件会导致卡顿、音频滞后以及更…

HTTP/2 中的漏洞

另一个热门漏洞是 CVE-2023-44487。 该漏洞与 HTTP/2 协议实施中的一个缺陷有关&#xff0c;可用于实施 DDoS 攻击。使用该漏洞的攻击被命名为 HTTP/2 快速重置。 为什么它很危险 要利用该漏洞&#xff0c;攻击者需要在 HTTP/2 会话中打开大量请求&#xff0c;然后在不等待服…

mysql面试题49:MySQL中不同text数据类型的最大长度

该文章专注于面试&#xff0c;面试只要回答关键点即可&#xff0c;不需要对框架有非常深入的回答&#xff0c;如果你想应付面试&#xff0c;是足够了&#xff0c;抓住关键点 面试官&#xff1a;MySQL中TEXT数据类型的最大长度 在MySQL中&#xff0c;TEXT数据类型用于存储较大…