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

news2024/11/27 8:46:17

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 UnityEngine;

public class Demo2 : MonoBehaviour
{
    public HyperlinkText text;
    // Start is called before the first frame update
    void Start()
    {
        Debug.Log("啊啊啊啊");
        text.onHyperlinkClick = OnClickText;
    }

    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.coloradmin.cn/o/1092512.html

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

相关文章

Mybatis学习笔记注解/xml映射/动态SQL%%%Mybatis教程

介绍 Mybatis 是一款优秀的持久层框架&#xff0c;用于简化 JDBC 的开发 MyBatis中文网 Mybatis 入门 快速入门 步骤 创建 SpringBoot 工程、数据库表 user、实体类 User引入 Mybatis 相关依赖&#xff0c;配置 Mybatis&#xff08;数据库连接信息&#xff09;编写 SQL 语…

adb调试Linux嵌入式设备记录

1. ADB的全称为Android Debug Bridge&#xff0c;调试设备或调试开发的Android APP。 2.adb的windows下载安装路径&#xff1a;SDK 平台工具版本说明 | Android 开发者 | Android Developers 3.linux中安装adb,参考该链接&#xff1a; https://www.cnblogs.com/androidsu…

Springboot+vue的财务管理系统(有报告),Javaee项目,springboot vue前后端分离项目。

演示视频&#xff1a; Springbootvue的财务管理系统&#xff08;有报告&#xff09;&#xff0c;Javaee项目&#xff0c;springboot vue前后端分离项目。 项目介绍&#xff1a; 本文设计了一个基于Springbootvue的前后端分离的财务管理系统&#xff0c;采用M&#xff08;model…

基于Seata的分布式事务方案

在Seata中&#xff0c;有4种分布式事务实现方案 XA、AT、TCC、Saga 其中XA利用了数据库的分布式事务特性&#xff0c;AT相当于框架去控制事务回滚。TCC手写三个方法&#xff0c;saga手写两个方法。 AT的性能和编写比较折中&#xff0c;是最常用的一种。TCC一些视频教程中介绍…

windows系统安装openssl并且转换证书格式

概述 碎碎念&#xff0c;如果你有MAC电脑&#xff0c;就别折腾了&#xff0c;直接用MAC电脑吧,不用安装直接用openssl 本文主要讲到了openssl的基本使用方法&#xff0c;开发环境为windows&#xff0c;开发工具为VS2019.本文主要是说明openssl如何使用&#xff0c;不介绍任何理…

判断某点是否在三角形内(Python)

已知三角形的三个顶点坐标&#xff0c;判断某个点是否在三角形中&#xff08;在三角形的边上&#xff0c;我们也视作在三角形中&#xff09;&#xff0c;我们提供不同的方法。 方法1&#xff1a;内角和等于360 方法2&#xff1a;等面积法 即对于△ABC内的某一点P&#xff0c;…

LInux文件权限相关知识介绍

LInux文件权限相关知识分享&#x1f60e; 前言&#x1f64c;Linux相关权限的概念&#xff1a;文件类型基本权限文件访问权限的相关设置方法chmod① 用户表示符/-权限字符②三位8进制数字 总结撒花&#x1f49e; &#x1f60e;博客昵称&#xff1a;博客小梦 &#x1f60a;最喜欢…

java+springboot+vue高校毕业生就业统计管理系统022xr

系统的设计与实现采用Spring、SpringMVC和MyBatis作为主体框架,系统设计遵循界面层、业务逻辑层和数据访问层的Web开发三层架构。采用B/S结构,使得系统更加容易维护。系统的设计与实现主要实现角色有管理员和用户,管理员在后台管理用户表模块、token表模块、生源信息模块、招聘…

代码随想录Day19 LeetCode T669修剪二叉搜索树 LeetCode T108将有序数组转化为二叉搜索树 T538 把二叉搜索树转化为累加树

LeetCode T669 修剪二叉搜索树 题目链接:669. 修剪二叉搜索树 - 力扣&#xff08;LeetCode&#xff09; 题目思路 这题我们有几个思路需要避坑,首先我们不能这样想,比如遇见比low值还小的节点值,不能直接返回null,而是考虑该节点的右子树有没有符合题目需求的节点值存在,同理删…

Java学习之TreeSet

Java TreeSet 底层是红黑树实现 将元素插入TreeSet add() - 将指定的元素插入集合 addAll() - 将指定集合的所有元素插入集合 import java.util.TreeSet;class Main {public static void main(String[] args) {TreeSet<Integer> evenNumbers new TreeSet<>();/…

二十三、【五种图层】

文章目录 像素图层智能图层文字图层形状图层调整图层 像素图层 像素图层由空白像素图层组成&#xff0c;上边没有任何颜色&#xff0c;如下图&#xff0c;我们可以使用画笔工具在空白相处图层上进行修改&#xff1a; 智能图层 智能图层可以记录该图层当前的数据在被编辑后可…

ShareX使用说明——优秀的录屏软件

ShareX初识 ShareX 是一个自由及开放源代码的截图录像软件&#xff0c;目前仅支持Windows系统。 项目源代码在GitHub平台上发布&#xff0c; 软件可以在Microsoft商店和Steam上下载。 ShareX is a free and open source program that lets you capture or record any area of y…

VPN的不同种类及现网应用场景

学习目标&#xff1a; 1. 企业为什么要部署VPN&#xff1f; 2. 常见的VPN有哪些种类&#xff1f; 3. VPN如何保障数据传输安全&#xff1f; -- VPN - 虚拟私有网络 - 企业最常用的功能之一 - 增值型业务 -- 需求&#xff1a;大一些公司 分公司 - 管理问题 --…

通过jsoup抓取谷歌商店评分

文章目录 背景实现是否下架预警评分 总的工具类,测试 背景 在谷歌上面发布包,有时候要看看评分,有时候会因为总总原因被下架,希望后台能够对评分进行预警,和下架预警 实现 测试地址: https://play.google.com/store/apps/details?idcom.tencent.mm 通过jsoup解析页面,然后获…

项目管理之六大目标及成功方程式

项目管理的六大目标分别是范围、质量、时间、成本、收益和风险。在项目开始之前&#xff0c;需要明确了解项目的范围&#xff0c;并在项目执行过程中对范围进行严格控制&#xff0c;确保项目不偏离既定的范围。同时&#xff0c;需要明确项目的质量标准和预期成果&#xff0c;然…

采用 guidance 提高大模型输出的可靠性和稳定性

本文首发于博客 LLM 应用开发实践 在复杂的 LLM 应用开发中&#xff0c;特别涉及流程编排和多次 LLM 调用时&#xff0c;每次的 Prompt 设计都取决于前一个步骤的大模型输出。如何避免大语言模型的"胡说八道"&#xff0c;以提高大语言模型输出的可靠性和稳定性&#…

1.13.C++项目:仿muduo库实现并发服务器之TcpServer模块的设计

文章目录 一、LoopThreadPool模块二、实现思想&#xff08;一&#xff09;管理&#xff08;二&#xff09;流程&#xff08;三&#xff09;功能设计 三、代码 一、LoopThreadPool模块 TcpServer模块&#xff1a; 对所有模块的整合&#xff0c;通过 tcpserver 模块实例化的对象&…

C++指针解读(3)-- 指针变量作为函数参数

函数执行是通过系统栈来实现的&#xff0c;系统栈分为若干个栈帧。栈帧就是函数运行的环境&#xff0c;每个函数在被调用时都会在系统栈区形成一个叫栈帧的结构。一次函数调用相关的数据保存在栈帧中&#xff0c;比如函数参数、函数的局部变量、函数执行完后的返回地址等数据。…

【LeetCode刷题(数据结构)】:另一颗树的子树

给你两棵二叉树 root 和 subRoot 检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子…

PCL点云处理之基于强度特征的SIFT关键点提取法 (二百一十五)

PCL点云处理之基于强度特征的SIFT关键点提取法 (二百一十五) 一、算法介绍二、具体实现1.代码2.效果一、算法介绍 继续SIFT关键点的提取介绍,之前已经基于高程和颜色分别提取了关键点,这里是基于强度信息,若遇到文件无法读取强度问题,请参考上一篇博文,下面是具体的实现…