Unity TMP Inputfield 输入框 框选 富文本 获取真实定位

一、带富文本标签的框选是什么

UGUI的InputField提供了selectionAnchorPosition和selectionFocusPosition,开始选择时的光标下标和当前光标下标

对于未添加富文本标签时,直接通过以上两个值,判断一下框选方向(前向后/后向前),进而对inputfield的text内容进行字符串拆分即可

相关基础内容可以看一些参考博客

https://blog.51cto.com/u_15296378/7884559

但是对于有富文本标签的inputfield,这两个值,返回的只是表面看起来的索引,并没有包含富文本标签。

举个栗子:

对于一段普通文本(未添加富文本):今天没有下雨

你想选择"没有下",那么索引应该是,2和4

但是对于一段富文本内容:今天<b>没有</b>下雨

你想选择"没有下",那么真实索引应该是,5和12,

那么根据实际索引,拆分后的字符串是这样"没有</b>下"

然而你再使用selectionAnchorPosition和selectionFocusPosition尝试获取时,依然得到的是2和4,

那么根据原索引,拆分后的字符串是这样"<b>",就完全被富文本标签干扰了

截止目前还没有或者我没找到官方直接可以使用的,支持带有富文本标签的,框选或选中内容定位获取接口。因此只能自己计算

二、获取真实富文本标签定位的大致思路

为了获取真实定位,这里我们先不考虑框选,先只考虑一个字符的位置。

还是以今天<b>没有</b>下雨为例

我想获取"没"的真实定位,只需要在原索引加上其前面富文本标签"<b>"的长度即可

那么2就变成了5

进而扩展一下,今天<b><i><color=yellow>没有</color></i></b>下雨 ,对于这个文本

想获取"没"的真实定位,就需要将"没"前面所有富文本标签的长度都加上

那么写一个方法,去除目标索引前的第一个富文本标签,并记录去除后的字符串和被去除标签的字符数量,然后递归调用自己,直到没有富文本标签之后结束

计算框选时第二个字的定位时,也是同理(这里后面框选时,计算的调节稍有不同,下文讲)

如果想要返回的内容中,不包含富文本标签,类似上图中的打印结果,只要"车10辆"三个字,那么使用一个正则字符串替换即可

cs 复制代码
//移除选定部分,所有富文本标签
    public string RemoveRichTextTags(string text) {
        return System.Text.RegularExpressions.Regex.Replace(text, "<.*?>", string.Empty);
    }

三、代码实现

在unity创建RichTextTagHandler类

这里先假定,只有一个InputField被编辑,无切换

先创建一个全局变量,用来保存某次计算的真实索引

cs 复制代码
private int total = 0;

创建RemoveFrontRichTag方法,用于移除,指定字符串,目标索引前,第一个富文本标签

cs 复制代码
/// <summary>
/// 从前往后,移除第一个富文本标签,计数标签字符数量,并将移除后的字符串返回
/// </summary>
/// <param name="aimString">待移除富文本标签的目标字符串</param>
/// <param name="surfaceIndex">原定位</param>
/// <param name="count">用来计数用的, 标记已经移除的富文本标签字符长度</param>
/// <returns>阶段性返回当前移除的富文本标签字符数量,递归后最终返回所有符合要求的富文本标签字符总数量</returns>
private string RemoveFrontRichTag(string aimString,int surfaceIndex, ref int count) {
    //先尝试定位'<'
    int meet = aimString.IndexOf('<');

    //如果定位不到,或者定位超过了原支付长度,说明标签在我们所选字符后面或者无富文本标签了,方法直接返回,并将数量count设置为0
    if (meet == -1 || meet > surfaceIndex) {
        count = 0;
        return aimString;
    }

    //成功定位到'<'后,继续定位'>'
    int leave = aimString.IndexOf('>');

    //将字符串拆分成去掉<>及其内部内容
    string newString = aimString.Substring(0, meet) + aimString.Substring(leave + 1, aimString.Length - leave - 1);

    //计算去掉部分的字符数量
    int length = leave - meet + 1;
    count += length;

    //Debug.Log(newString + " meet=" + meet + " leave=" + leave + " length=" + length);

    //把去掉已计数的标签后的字符串,返回
    return newString;
}

创建TryGetRealIndex方法,用于递归调用,移除目标索引前的所有富文本标签

cs 复制代码
/// <summary>
/// 尝试获取真实索引,递归方法,
/// </summary>
/// <param name="richText"></param>
/// <param name="surfaceIndex"></param>
/// <param name="isEndPoint"></param>
/// <returns></returns>
private int TryGetRealIndex(string richText, int surfaceIndex,bool isEndPoint) {

    string newString = richText;
    int count = 0;

    //先尝试定位'<',并获取它的定位
    int mark = newString.IndexOf('<');

    //-1是没找到,没找到或大于,原本的定位,那么代表,原本定位之前已经没有'<'了,直接返回结束

    if ((isEndPoint && (mark == -1 || mark >= surfaceIndex)) || //如果是结尾的点,那么判断mark时添加等号,防止后侧遗留标签整体
        (!isEndPoint && (mark == -1 || mark > surfaceIndex))) {//如果不是结尾的点,那么判断mark时不加等号,判断前侧遗留标签整体
        return total;
    }
    //如果在原定位前,找到了'<',那么走一遍清除最前的富文本标签并计数的方法
    else {
        //清除一次标签,并将清除后的字符串保存
        newString = RemoveFrontRichTag(newString, surfaceIndex, ref count);
        //加入计数
        total += count;
        //回调,继续判断,原本定位前,是否有富文本标记
        TryGetRealIndex(newString, surfaceIndex, isEndPoint);
    }

    //递归完成后,返回最终的富文本标签字符总数
    return total;
}

最后创建GetRealIndex,用于获取最终索引,并返回

cs 复制代码
/// <summary>
/// 获取选定部分,排除富文本标签后(富文本标签也算作数量),字符的真实位置
/// </summary>
/// <param name="inputField"></param>
/// <param name="surfaceIndex"></param>
/// <param name="isEndPoint">
/// 默认为false,从选定位置往前,所有富文本标签<>全部排除(通常为选定部分的"前面"点)
/// 传入true后,从选定位置开始,不仅往前,而且往后,所有富文本标签<>全部排除(通常为选定部分"后面"点)
/// 注意:选定时,从前往后选和从后往前选,"前面"和"后面"的点要判断一下
/// 扩展:如果"前面"点传入true,而"后面"点传入false,那么可以获取选定区域开始,前后所有的富文本标签都会被选定(默认是清除前后所有富文本标签)
/// </param>
/// <returns></returns>
public int GetRealIndex(TMP_InputField inputField, int surfaceIndex,bool isEndPoint=false) {
    //
    string richText = inputField.text;

    //所有,surfaceIndex前,富文本标签所占的字符总数
    int allRichTagCharCount = 0;
    total = 0;//每次开始前,重置total为0

    //递归获取富文本标签所占的字符总数
    allRichTagCharCount = TryGetRealIndex(richText, surfaceIndex,isEndPoint);

    //最终实际的定位是,富文本字符总数与surfaceIndex表定位的合
    return allRichTagCharCount + surfaceIndex;
}

完整的脚本长这样

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class RichTextTagHandler {

    private int total = 0;

    /// <summary>
    /// 获取选定部分,排除富文本标签后(富文本标签也算作数量),字符的真实位置
    /// </summary>
    /// <param name="inputField"></param>
    /// <param name="surfaceIndex"></param>
    /// <param name="isEndPoint">
    /// 默认为false,从选定位置往前,所有富文本标签<>全部排除(通常为选定部分的"前面"点)
    /// 传入true后,从选定位置开始,不仅往前,而且往后,所有富文本标签<>全部排除(通常为选定部分"后面"点)
    /// 注意:选定时,从前往后选和从后往前选,"前面"和"后面"的点要判断一下
    /// 扩展:如果"前面"点传入true,而"后面"点传入false,那么可以获取选定区域开始,前后所有的富文本标签都会被选定(默认是清除前后所有富文本标签)
    /// </param>
    /// <returns></returns>
    public int GetRealIndex(TMP_InputField inputField, int surfaceIndex,bool isEndPoint=false) {
        //
        string richText = inputField.text;

        //所有,surfaceIndex前,富文本标签所占的字符总数
        int allRichTagCharCount = 0;
        total = 0;//每次开始前,重置total为0

        //递归获取富文本标签所占的字符总数
        allRichTagCharCount = TryGetRealIndex(richText, surfaceIndex,isEndPoint);

        //最终实际的定位是,富文本字符总数与surfaceIndex表定位的合
        return allRichTagCharCount + surfaceIndex;
    }

    /// <summary>
    /// 尝试获取真实索引,递归方法,
    /// </summary>
    /// <param name="richText"></param>
    /// <param name="surfaceIndex"></param>
    /// <param name="isEndPoint"></param>
    /// <returns></returns>
    private int TryGetRealIndex(string richText, int surfaceIndex,bool isEndPoint) {

        string newString = richText;
        int count = 0;

        //先尝试定位'<',并获取它的定位
        int mark = newString.IndexOf('<');

        //-1是没找到,没找到或大于,原本的定位,那么代表,原本定位之前已经没有'<'了,直接返回结束

        if ((isEndPoint && (mark == -1 || mark >= surfaceIndex)) || //如果是结尾的点,那么判断mark时添加等号,防止后侧遗留标签整体
            (!isEndPoint && (mark == -1 || mark > surfaceIndex))) {//如果不是结尾的点,那么判断mark时不加等号,判断前侧遗留标签整体
            return total;
        }
        //如果在原定位前,找到了'<',那么走一遍清除最前的富文本标签并计数的方法
        else {
            //清除一次标签,并将清除后的字符串保存
            newString = RemoveFrontRichTag(newString, surfaceIndex, ref count);
            //加入计数
            total += count;
            //回调,继续判断,原本定位前,是否有富文本标记
            TryGetRealIndex(newString, surfaceIndex, isEndPoint);
        }

        //递归完成后,返回最终的富文本标签字符总数
        return total;
    }

    /// <summary>
    /// 从前往后,移除第一个富文本标签,计数标签字符数量,并将移除后的字符串返回
    /// </summary>
    /// <param name="aimString">待移除富文本标签的目标字符串</param>
    /// <param name="surfaceIndex">原定位</param>
    /// <param name="count">用来计数用的, 标记已经移除的富文本标签字符长度</param>
    /// <returns>阶段性返回当前移除的富文本标签字符数量,递归后最终返回所有符合要求的富文本标签字符总数量</returns>
    private string RemoveFrontRichTag(string aimString,int surfaceIndex, ref int count) {
        //先尝试定位'<'
        int meet = aimString.IndexOf('<');

        //如果定位不到,或者定位超过了原支付长度,说明标签在我们所选字符后面或者无富文本标签了,方法直接返回,并将数量count设置为0
        if (meet == -1 || meet > surfaceIndex) {
            count = 0;
            return aimString;
        }

        //成功定位到'<'后,继续定位'>'
        int leave = aimString.IndexOf('>');

        //将字符串拆分成去掉<>及其内部内容
        string newString = aimString.Substring(0, meet) + aimString.Substring(leave + 1, aimString.Length - leave - 1);

        //计算去掉部分的字符数量
        int length = leave - meet + 1;
        count += length;

        //Debug.Log(newString + " meet=" + meet + " leave=" + leave + " length=" + length);

        //把去掉已计数的标签后的字符串,返回
        return newString;
    }

    #region Utility

    //移除选定部分,所有富文本标签
    public string RemoveRichTextTags(string text) {
        return System.Text.RegularExpressions.Regex.Replace(text, "<.*?>", string.Empty);
    }

    //移除选定部分,所有颜色标签
    public string RemoveRichTextTags_color(string text) {
        return System.Text.RegularExpressions.Regex.Replace(text, "<color=.*?>|</color>", string.Empty);
    }

    //移除选定部分,所有加粗标签
    public string RemoveRichTextTags_B(string text) {
        return System.Text.RegularExpressions.Regex.Replace(text, "<b>|</b>", string.Empty);
    }

    //移除选定部分,所有Size标签
    public string RemoveRichTextTags_Size(string text) {
        return System.Text.RegularExpressions.Regex.Replace(text, "<size=.*?>|</size>", string.Empty);
    }

    #endregion
}

使用时,类似这样

cs 复制代码
private void Update() {
    if (Input.GetMouseButtonUp(0)) {
        OnMouseUpFromIpf();
    }

}

#region 主要代码

private void OnMouseUpFromIpf() {
    if (EventSystem.current.currentSelectedGameObject == ipf.gameObject) {

        int startPos = ipf.selectionAnchorPosition;//起始选择的位置
        int endPos = ipf.selectionFocusPosition;//结束时的位置

        if (startPos == endPos) return;//如果起始结束相等,那么相当于就点了一下,没选择内容

        string selectedStr = "";

        if (startPos < endPos) {//正常从前往后选的情况

            // 获取可见文本的起始位置,从选定Index开始,排除前面所有富文本标签
            currentInsideBeginIndex = handler.GetRealIndex(ipf, startPos);
            //获取可见文本的结束位置,从选定Index开始,后面的所有富文本标签,也要排除
            currentInsideEndIndex = handler.GetRealIndex(ipf, endPos, true);//此处传入true,即可以排除index后面的所有富文本标签
        }
        else if (startPos > endPos) {//从后往前选的情况

            currentInsideBeginIndex = handler.GetRealIndex(ipf, endPos);//反选,endPos是"前面的点"
            currentInsideEndIndex = handler.GetRealIndex(ipf, startPos, true);//反选,startPos是"后面的点"
        }

        //获取选定部分的字符串,排除选定区域开始,前后所有的富文本标签
        //注意:如果需要,以选定区域为基准,获取区域前后部分的所有富文本标签(而不是排除),那么需要将BeginIndex的GetRealIndex中IsEndPoint传入true,而EndIndex的GetRealIndex中IsEndPoint传入false
        //类似一个加粗区域,<b>A</b>,目前选定A,那么获取到的是"A",若是以下面的写法反向设置IsEndPoint,那么获取到的是"<b>A</b>"。后面这么获取,可以方便对标签进行判断和修改
        selectedStr = ipf.text.Substring(currentInsideBeginIndex, currentInsideEndIndex - currentInsideBeginIndex);

        Debug.Log(currentInsideBeginIndex + " " + currentInsideEndIndex + " " + selectedStr+" "+startPos+" "+endPos);
        ipf_console.text = selectedStr;
    }
}

演示

其他扩展功能,只需要对字符串进行编辑即可

相关推荐
躺下睡觉~4 小时前
Unity-Transform类-父子关系
java·unity·游戏引擎
躺下睡觉~4 小时前
Unity-Transform类-缩放和看向
unity·游戏引擎
君莫愁。6 小时前
【Unity】检测鼠标点击位置是否有2D对象
unity·c#·游戏引擎
咩咩觉主7 小时前
Unity实战案例全解析:PVZ 植物卡片状态分析
unity·c#·游戏引擎
蓝裕安10 小时前
伪工厂模式制造敌人
开发语言·unity·游戏引擎
谢泽浩14 小时前
Unity 给模型贴上照片
unity·游戏引擎
z2014z14 小时前
Unity Resource System 优化笔记
unity·游戏引擎
王维志14 小时前
Unity 高亮插件HighlightPlus介绍
unity·游戏引擎
zaizai100715 小时前
我的demo保卫萝卜中的技术要点
unity
菌菌巧乐兹16 小时前
Unity 百度AI实现无绿幕拍照抠像功能(详解版)
人工智能·百度·unity