unity UGUI中获取点击位置处的URL链接

更新:增加自适应支持。

需求是,我们在一个text组件中像写网页那样写入链接,然后点击这个链接,就能访问配置的网页啥的。比如:

<a href="hello">链接文本</a></summary>

最终的效果如下:

图中,image区域就是各个链接的点击范围。原理是获取text中,每个字符的位置,然后算出每个链接对应的点击区域,最后返回鼠标点到的那个区域的链接。代码比较简单,就直接写点注释看吧。实现是继承了text组件,当然写成静态方法传入text来计算也可以。

比较一下网上搜到的其他方案,这个方法不用重载mesh,效率应该是比较高的。

#define TEST_CheckClickURL
using System.Collections.Generic;
using System.Text.RegularExpressions;
using UnityEngine;
using UnityEngine.Profiling;
using UnityEngine.UI;

public class TestClickURL : Text
{
    public Button button;
    public void OnButtonClick()
    {
        Debug.Log(CheckClickURL()?.url);
    }

    // 定义返回的结果
    public class CheckClickURLResult
    {
        public string url;
        public string text;
        public Rect rect;
        public CheckClickURLResult(string url, string text, Rect rect)
        {
            this.url = url;
            this.text = text;
            this.rect = rect;
        }
    }
    private static Regex hrefRegex =
        new Regex(@"<a href=([^>\n\s]+)>(.*?)(</a>)",
            RegexOptions.Singleline);
    /// <summary> 计算点击到的URL文本内容,返回网址
    /// 格式如下:<a href="hello">链接文本</a></summary>
    public CheckClickURLResult CheckClickURL()
    {
        Profiler.BeginSample("CheckClickURL");
        if (hrefRegex == null)
            hrefRegex = new Regex(
                @"<a href=([^>\n\s]+)>(.*?)(</a>)", RegexOptions.Singleline);
        InitDebugGOList();

        // 将点击位置从屏幕坐标转为本地坐标
        RectTransformUtility.ScreenPointToLocalPointInRectangle(
            this.rectTransform, Input.mousePosition,
            null, out var mouseLocalPosition);
        //注意使用UI相机

        // 获取生成的文本数据。
        // characters 保存了每个字符左上角的位置。
        // lines 保存了每行开始字符ID,和行高。
        var generator = cachedTextGenerator;
        var charList = generator.characters;
        var lineList = generator.lines;
        var textStr = text;

        // 正则表达式查找链接文本
        var matchs = hrefRegex.Matches(textStr);
        foreach (Match match in matchs)
        {
            var urlGroup = match.Groups[1];
            var textGroup = match.Groups[0];
            var textStartIndex = textGroup.Index;
            var textEndIndex = textGroup.Index + textGroup.Length;
            // 我们的字符可能是换行的,所以要按行分割。
            // 倒着遍历就很容易获取每行开始和结束位置。
            var lineEndIndex = charList.Count - 1;
            for (int i = lineList.Count - 1; i >= 0; i--)
            {
                var lineStartIndex = lineList[i].startCharIdx;
                // 处理换行后的截取
                var realStart = Mathf.Max(lineStartIndex, textStartIndex);
                var realEnd = Mathf.Min(lineEndIndex, textEndIndex);
                // 本行没有链接内容的情况
                if (realStart > realEnd) continue;
                // 问题简化成单行的点击检查,提个函数继续处理。
                var result = CheckLine(realStart, realEnd, lineList[i].height, mouseLocalPosition, out var rect);
                if (result) return new CheckClickURLResult(urlGroup.Value, textGroup.Value, rect);

                //Debug.Log($"{start}/{end}");
                lineEndIndex = lineStartIndex - 1;
            }
        }
        Profiler.EndSample();
        return null;
    }

    public bool CheckLine(int start, int end, float lineHeight, Vector2 mouseLocalPosition, out Rect rect)
    {
        // 获取生成的文本数据。
        var charList = cachedTextGenerator.characters;
        var startPoint = charList[start].cursorPos;
        var endPoint = charList[end].cursorPos;

        // 直接计算出本行中链接可点击区域。
        var x = startPoint.x;
        var y = startPoint.y - lineHeight;
        var width = endPoint.x - startPoint.x;
        var height = lineHeight;
		var scaleFactor = canvas != null ? canvas.scaleFactor :1;
		// 考虑屏幕自适应的影响
        rect = new Rect(x / scaleFactor, y / scaleFactor,
            width / scaleFactor, height / scaleFactor);

        var result = rect.Contains(mouseLocalPosition);
        CreateDebugImage(rect, result);
        return result;
    }

#if TEST_CheckClickURL
    // 测试用。生成空image展示出点击判定范围。
    public static List<GameObject> debugGOList;
    public void CreateDebugImage(Rect rect, bool contains)
    {
        Debug.Log($"rect={rect}");
        var go = new GameObject("DebugImage",
            typeof(RectTransform), typeof(Image));
        debugGOList.Add(go);
        var rtf = go.GetComponent<RectTransform>();
        rtf.SetParent(transform);
        rtf.pivot = Vector2.zero;
        rtf.anchorMin = Vector2.one / 2;
        rtf.anchorMax = Vector2.one / 2;
        rtf.sizeDelta = rect.size;
        rtf.localScale = Vector3.one;
        rtf.rotation = Quaternion.identity;
        rtf.anchoredPosition = rect.position - rectTransform.rect.center;
        // 点击到的那个范围展示为红色。
        if (contains)
            go.GetComponent<Image>().color = Color.red;
    }
    public void InitDebugGOList()
    {
        if (debugGOList == null)
            debugGOList = new List<GameObject>();
        debugGOList.ForEach(p => Destroy(p));
    }
#else
        public void CreateDebugImage(Rect rect, bool contains) { }
        public void InitDebugGOList() { }
#endif
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值