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

news2024/11/23 2:38:18

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

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:<size=26></size>  <color=#ffffff></color>
        private static readonly string[] _uguiSymbols2 = { "size", "color" };

        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>背着手踱着。</color><href=第一段第一句>路上只我一个人</href>,这一片天地好像是我的;我也像超出了平常旳自己,到了另一世界里。我爱热闹,也爱冷静;爱群居,也爱独处。像今晚上,一个人在这苍茫旳月下,什么都可以想,什么都可以不想,便觉是个自由的人。白天里一定要做的事,一定要说的话,现在都可不理。这是独处的妙处,我且受用这无边的荷香月色好了。

<href=第二段第一句>曲曲折折的荷塘上面</href>,弥望旳是田田的叶子。叶子出水很高,像亭亭旳舞女旳裙。层层的叶子中间,零星地点缀着些白花,有袅娜(niǎo,nuó)地开着旳,有羞涩地打着朵儿旳;正如一粒粒的明珠,又如碧天里的星星,又如刚出浴的美人。微风过处,送来缕缕清香,仿佛远处高楼上渺茫的歌声似的。这时候叶子与花也有一丝的颤动,像闪电般,霎时传过荷塘的那边去了。叶子本是肩并肩密密地挨着,这便宛然有了一道凝碧的波痕。叶子底下是脉脉()的流水,遮住了,不能见一些颜色;而叶子却更见风致了。

月光如流水一般,静静地泻在这一片叶子和花上。薄薄的青雾浮起在荷塘里。叶子和花仿佛在牛乳中洗过一样;又像笼着轻纱的梦。虽然是满月,天上却有一层淡淡的云,所以不能朗照;但我以为这恰是到了好处——酣眠固不可少,小睡也别有风味的。月光是隔了树照过来的,高处丛生的灌木,落下参差的斑驳的黑影,峭楞楞如鬼一般;弯弯的杨柳的稀疏的倩影,却又像是画在荷叶上。塘中的月色并不均匀;但光与影有着和谐的旋律,<href=第三段>如梵婀(ē)(英语violin小提琴的译音)上奏着的名曲</href>。

荷塘的四面,远远近近,高高低低都是树,而杨柳最多。<href=第四段>这些树将一片荷塘重重围住</href>;只在小路一旁,漏着几段空隙,像是特为月光留下的。树色一例是阴阴的,乍看像一团烟雾;但杨柳的丰姿,便在烟雾里也辨得出。树梢上隐隐约约的是一带远山,只有些大意罢了。树缝里也漏着一两点路灯光,没精打采的,是渴睡人的眼。这时候最热闹的,要数树上的蝉声与水里的蛙声;但热闹是它们的,我什么也没有。

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

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

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

相关文章

微信小程序备案流程操作详解,值得收藏

目录 一、小程序备案法律法规参考 二、备案前准备 2.1 备案入口 2.1.1、未上架小程序 2.1.2、已上架小程序 (二)备案类型 (三)备案材料准备 3.1、小程序备案材料 3.2、前置审批材料 3.3、个人备案 3.4、非个人备案 三、备案整体流程 (一)备案信息填写 1、主体信息…

两种方式获取Stream流的方式

java.util.stream.Stream<T> 是Java 8 新加入的最常用的流接口。&#xff08;这并不是一个函数式接口&#xff09;获取一个流有以下两种方式 所有的 Collection集合 都可以通过stream默认方法获取流 Stream接口 的静态方法of可以获取数组对应的流 package com.csdn.s…

想要精通算法和SQL的成长之路 - 分割数组的最大值

想要精通算法和SQL的成长之路 - 分割数组的最大值 前言一. 分割数组的最大值1.1 二分法 前言 想要精通算法和SQL的成长之路 - 系列导航 一. 分割数组的最大值 原题链接 首先面对这个题目&#xff0c;我们可以捕获几个关键词&#xff1a; 非负整数。非空连续子数组。 那么我…

选实验室超声波清洗机易忽视的内容?小型清洗机的优点有?

实验室超声波清洗机如今在行业内占据着重要的一席之地&#xff0c;摒弃了传统模式&#xff0c;坚持以超声波为主的清洗方式&#xff0c;在市场中获得的反响强烈。服务好&#xff0c;有诚信的实验室超声波清洗机能够消除客户的后顾之忧&#xff0c;工作人员会以真诚态度向客户提…

机器人制作开源方案 | 棘轮小车

1. 运动功能说明 棘轮小车&#xff08;Ratchet Car&#xff09;是一种基于棘轮原理设计的小型车辆&#xff0c;它结合了棘轮机制和移动装置&#xff0c;用于特定的应用场景&#xff0c;这种设计使得小车能够实现单向移动或防止逆向移动。棘轮小车的主要特点包括&#xff1a; …

【藏经阁一起读】(72)__《Hologres 一站式实时数仓客户案例集》

【藏经阁一起读】&#xff08;72&#xff09; __《Hologres 一站式实时数仓客户案例集》 目录 【藏经阁一起读】&#xff08;72&#xff09; 一、实时数仓概念 二、Hologres 三、Hologres 一站式实时数仓客户案例集 3.1、电商 3.1.1、实时数仓 Hologres 首次走进阿里淘特…

BUUCTF jarvisoj_level0 1

目录 一、分析二、EXP三、本地打不通&#xff1f;远程能打通&#xff1f; 一、分析 查看文件信息 关键信息 64位程序栈不可执行 IDA64反汇编 进入第一个函数 栈溢出 shift F12查找字符串 点进去 发现是一个后门函数 二、EXP from pwn import *context.arch amd64 #…

Godot 单元测试

前言 单元测试是我们常用的功能&#xff0c;Godot作为一个游戏&#xff0c;单元测试和热重载是我们常用的功能。这里我们讲解最简单的单元测试的情况。 Godot 配置 我们添加一个最简单的节点&#xff0c;挂载一个最简单的脚本。 添加测试方法&#xff08;只能是静态方法&…

Flex 词法分析实验实现(电子科技大学编译技术Icoding实验)

Flex 词法分析 此为电子科技大学编译技术 实验1&#xff1a;词法分析 将具体实现中的三个文件和自己的实验报告一起上传才能通过 根据词法分析实验中给定的文法&#xff0c;利用 flex 设计一词法分析器&#xff0c;该分析器从标准输入读入源代码后&#xff0c;输出单词的类别编…

Linux线程同步实例

线程同步实例 1. 生产消费者模型基本概念2. 基于BlockingQueue的生产者消费者模型3. 基于环形队列的生产消费模型4. 线程池 1. 生产消费者模型基本概念 生产者消费者模型是一种常用的并发设计模式&#xff0c;它可以解决生产者和消费者之间的速度不匹配、解耦、异步等问题。生…

Vue 绑定style和class

在应用界面中&#xff0c;某些元素的样式是动态的。class 与 style 绑定就是专门用来实现动态样式效果的技术。 如果需要动态绑定 class 或 style 样式&#xff0c;可以使用 v-bind 绑定。 绑定 class 样式【字符串写法】 适用于&#xff1a;类名不确定&#xff0c;需要动态指…

Stm32_标准库_8_ADC_光敏热敏传感器_测数值

在测量光敏传感器数值得基础上手动将通道改成热敏传感器通道即可 由于温度传感器的测量范围是-20 ~ 105摄氏度&#xff0c;所以输出温度得考虑带上符号这就需要在原有输出光照强度代码的基础上新添加几个函数 函数1&#xff1a; uint16_t AD_Getvailue(uint8_t ADC_Channel){…

六、DHCP实验

拓扑图&#xff1a; DHCP协议&#xff0c;给定一个ip范围使其自动给终端分配IP&#xff0c;提高了IP分配的效率 首先对PC设备选择DHCP分配ip 首先先对路由器的下端配置网关的ip 创建地址池&#xff0c;通过globle的方式实现DHCP ip pool 地址池名称 之后设置地址池的网关地址…

最大数【贪心3】

题目 分析 代码 class Solution { public:string largestNumber(vector<int>& nums) {vector<string> str;for(auto & x : nums){str.push_back(to_string(x));}sort(str.begin(),str.end(),[](const string& s1,const string& s2){return s1 s…

【JavaEE】_servlet程序的编写方法

目录 1. 创建项目 2. 引入依赖 3. 创建目录结构 3.1 在main目录下创建一个webapp目录 3.2 在webapp目录下创建一个WEB-INF目录 3.3 在WEB-INF目录下创建一个web.xml文件 3.4 在web.xml中进行代码编写 4. 编写代码 4.1 在java目录下创建类 4.2 打印"hello world&…

亚马逊测评安全吗?

测评可以说是卖家非常宝贵的财富&#xff0c;通过测评和广告相结合&#xff0c;可以快速有效的提升店铺的产品销量&#xff0c;提高转化&#xff0c;提升listing权重&#xff0c;但现在很多卖家找真人测评补单后店铺出现问题导致大家对测评的安全性感到担忧&#xff0c;因为真人…

基于php+thinphp+vue的商品购物商城网站

运行环境 开发语言&#xff1a;PHP 数据库:MYSQL数据库 使用框架:ThinkPHPvue 开发工具:VScode/Dreamweaver/PhpStorm等均可 项目简介 基于tpvue的商品定制交易网站实现前台与后台&#xff0c;用户前台&#xff1b;首页、商品信息、我的收藏、留言板、个人中心、后台管理、管…

【c语言】迷宫游戏

之前想写的迷宫游戏今天终于大功告成&#xff0c;解决了随机生成迷宫地图的问题&#xff0c;使用的是深度优先算法递归版本&#xff0c;之前的迷宫找通路问题用的是深度优先算法的非递归实现.之前写过推箱子&#xff0c;推箱子用到了人物的移动&#xff0c;以及碰到墙就不会走&…

如何解决网站被攻击的问题

在当今数字化时代&#xff0c;网站攻击已经成为互联网上的一个常见问题。这些攻击可能会导致数据泄漏、服务中断和用户信息安全问题。然而&#xff0c;我们可以采取一些简单的措施来解决这些问题&#xff0c;以确保网站的安全性和可用性。 使用强密码和多因素认证 密码是保护网…

今年这情况,还能不能选计算机了?

在知乎上看到一个有意思的问题&#xff0c;是劝退计算机的。 主要观点&#xff1a; 计算机从业人员众多加班&#xff0c;甚至需要99635岁危机秃头 综上所属&#xff0c;计算机不仅卷&#xff0c;而且还是一个高危职业呀&#xff0c;可别来干了。 关于卷 近两年确实能明显感觉…