Unity 超链接 Text

Unity 超链接 Text

之前用的是https://github.com/akof1314/uGUI_LinkImageText这个控件,结果发现点击事件是不对的。我将包围框显示出来后它是这样一种情况

包围框显示

从图片可以看出这个包围框是混乱的,那就难怪点击事件不准了。经排查发现是包围框的起始和结束的Index计算有错误。

详情是这样的,这个功能的本质是截取网址这一段,拿到他的开始和结束的Index,然后遍历Text渲染顶点的时候以开始Index为左,结束Index为右,生成一个包围框(有换行另处理)。那么这个包围框就是我们点击网址的相应区域。

而原版在计算这个Index的时候出现了失误,因为忽略了两个关键点:

  1. 富文本的语法代码没有渲染顶点,需剔除Index计算
  2. 空格、回车没有渲染顶点,需剔除Index计算
    也就是说,对于:
Baidu 网址:
<a href=https://www.baidu.com/>Baidu网址</a>

google 网址:
<a href=https://www.google.com/>google网址</a>

Baidu URL:
<a href=https://www.baidu.com/>https://www.baidu.com/</a>

google URL:
<a href=https://www.google.com/>https://www.google.com/</a>

这样的文本,我们需要剔除富文本html语法和空格、换行符后才能用来计算Index,就像这样:

Baidu网址:Baidu网址google 网址:google网址BaiduURLhttps://www.baidu.com/googleURL:https://www.google.com/

把所有不参与顶点计算的元素去掉才行。

如果想要改颜色,根据语法需要加标签,如:

Baidu 网址:
<a href=https://www.baidu.com/><color=red>Baidu网址</color></a>

google 网址:
<a href=https://www.google.com/>google网址</a>

Baidu URL:
<a href=https://www.baidu.com/>https://www.baidu.com/</a>

google URL:
<a href=https://www.google.com/>https://www.google.com/</a>

其中第一条的baidu网址显示就是红色的,当然,在计算Index的时候也要把标签刨除掉

显示如图,可以看到下面的包围框显示是完全正常的

修正后的包围框显示

完整代码:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.UI;

/// <summary>
/// TextHyperlink 支持Unity超链接富文本语法
/// 例如<a href=https://aws.amazon.com/privacy/>https://aws.amazon.com/privacy/</a>
/// 带颜色的超链接:
/// <a href=https://aws.amazon.com/privacy/><color=#FF0000>https://aws.amazon.com/privacy/</color></a>
/// </summary>
public class TextHyperlink : Text, IPointerClickHandler
{
    /// <summary>
    /// 超链接信息类
    /// </summary>
    private class HyperlinkInfo
    {
        //起始Index
        public int StartIndex;
        //结束Index
        public int EndIndex;
        //内容
        public string Name;
        //包围框
        public List<Rect> BoxList = new List<Rect>();
    }

    #region 私有变量
    //超链接正则
    private static Regex _hrefRegex = new Regex(@"<a href=([^>\n\s]+)>(.*?)(</a>)", RegexOptions.Singleline);
    //颜色正则
    private static Regex _colorRegex = new Regex(@"<color=([^>\n\s]+)>(.*?)(</color>)", RegexOptions.Singleline);
    //超链接信息列表
    private List<HyperlinkInfo> _hyperlinkInfoList = new List<HyperlinkInfo>();
    //显示包围盒范围,想用的话输入true
    private bool _showTestBound = true;
    #endregion

    #region 公有变量

    #endregion

    #region 生命周期
    protected override void OnPopulateMesh(VertexHelper toFill)
    {
        base.OnPopulateMesh(toFill);

        InitHyperlinkInfo();

        InitHyperlinkBox(toFill);
    }

    protected override void Start()
    {
    	//这个代码要在InitHyperlinkInfo();执行后才能执行,如果包围盒显示不出来就需要注意一下时序对不对。
        if (_showTestBound)
        {
            AddVisibleBound();
        }
    }
    #endregion

    #region 公有方法

    #endregion

    #region 动作
    public void OnPointerClick(PointerEventData eventData)
    {
        Vector2 localPoint;
        RectTransformUtility.ScreenPointToLocalPointInRectangle(
            rectTransform, eventData.position, eventData.pressEventCamera, out localPoint);

        foreach (HyperlinkInfo hyperlinkInfo in _hyperlinkInfoList)
        {
            var boxeList = hyperlinkInfo.BoxList;
            for (var i = 0; i < boxeList.Count; ++i)
            {
                if (boxeList[i].Contains(localPoint))
                {
                    //打开网址
                    //this.Log("TextHyperlink", hyperlinkInfo.Name);
                    Application.OpenURL(hyperlinkInfo.Name);
                    return;
                }
            }
        }
    }
    #endregion

    #region 私有方法
    /// <summary>
    /// 初始化连接信息
    /// </summary>
    private void InitHyperlinkInfo()
    {
        //这个值不用,就是个存根。要的是执行后面那个方法
        string outputText = GetOutputText(text);
    }

    /// <summary>
    /// 初始化连接包围框
    /// </summary>
    /// <param name="toFill"></param>
    private void InitHyperlinkBox(VertexHelper toFill)
    {
        UIVertex vert = new UIVertex();

        // 处理超链接包围框
        foreach (var hrefInfo in _hyperlinkInfoList)
        {
            hrefInfo.BoxList.Clear();

            //一个字符是四个顶点,所以Index要乘以4
            int startVertex = hrefInfo.StartIndex * 4;
            int endVertex = hrefInfo.EndIndex * 4;

            if (startVertex >= toFill.currentVertCount)
            {
                continue;
            }

            // 将超链接里面的文本顶点索引坐标加入到包围框
            toFill.PopulateUIVertex(ref vert, startVertex);
            var pos = vert.position;
            var bounds = new Bounds(pos, Vector3.zero);
            for (int i = startVertex; i < endVertex; i++)
            {
                if (i >= toFill.currentVertCount)
                {
                    break;
                }

                toFill.PopulateUIVertex(ref vert, i);
                pos = vert.position;
                if (pos.x < bounds.min.x) // 换行重新添加包围框
                {
                    hrefInfo.BoxList.Add(new Rect(bounds.min, bounds.size));
                    bounds = new Bounds(pos, Vector3.zero);
                }
                else
                {
                    bounds.Encapsulate(pos); // 扩展包围框
                }
            }

            hrefInfo.BoxList.Add(new Rect(bounds.min, bounds.size));
        }
    }

    /// <summary>
    /// 获取超链接解析后的最后输出文本
    /// </summary>
    /// <returns></returns>
    private string GetOutputText(string outputText)
    {
        StringBuilder stringBuilder = new StringBuilder();

        _hyperlinkInfoList.Clear();

        int strIndex = 0;

        foreach (Match match in _hrefRegex.Matches(outputText))
        {
            string appendStr = outputText.Substring(strIndex, match.Index - strIndex);
            stringBuilder.Append(appendStr);

            //空格和回车没有顶点渲染,所以要去掉
            stringBuilder = stringBuilder.Replace(" ", "");
            stringBuilder = stringBuilder.Replace("\n", "");

            int startIndex = stringBuilder.Length;

            //第一个是连接url,第二个是连接文本,跳转用url,计算index用文本
            Group urlGroup = match.Groups[1];
            Group titleGroup = match.Groups[2];

            //如果有Color语法嵌套,则还要继续扒,知道吧最终文本扒出来
            Match colorMatch = _colorRegex.Match(titleGroup.Value);

            if (colorMatch.Groups.Count > 3)
            {
                titleGroup = colorMatch.Groups[2];
            }

            stringBuilder.Append(titleGroup.Value);

            HyperlinkInfo hyperlinkInfo = new HyperlinkInfo
            {
                StartIndex = startIndex, 
                EndIndex = (startIndex + titleGroup.Length),
                Name = urlGroup.Value
            };

            strIndex = match.Index + match.Length;
            _hyperlinkInfoList.Add(hyperlinkInfo);
        }
        stringBuilder.Append(outputText.Substring(strIndex, outputText.Length - strIndex));
        return stringBuilder.ToString();
    }

    /// <summary>
    /// 添加可视包围框(测试用方法)
    /// </summary>
    private void AddVisibleBound()
    {
        int index = 0;

        foreach (var hyperLinkInfo in _hyperlinkInfoList)
        {
            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 hyperLinkInfo.BoxList)
            {
                GameObject gameObject = new GameObject();
                gameObject.name = string.Format("GOBoundBox[{0}]", hyperLinkInfo.Name);
                //gameObject.SetParent(this.gameObject);//替换成下面这个方法
				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;
            }
        }
    }
    #endregion
}

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值