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辆”三个字,那么使用一个正则字符串替换即可
//移除选定部分,所有富文本标签public string RemoveRichTextTags(string text) {return System.Text.RegularExpressions.Regex.Replace(text, "<.*?>", string.Empty);}
三、代码实现
在unity创建RichTextTagHandler类
这里先假定,只有一个InputField被编辑,无切换
先创建一个全局变量,用来保存某次计算的真实索引
private int total = 0;
创建RemoveFrontRichTag方法,用于移除,指定字符串,目标索引前,第一个富文本标签
/// <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设置为0if (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方法,用于递归调用,移除目标索引前的所有富文本标签
/// <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,用于获取最终索引,并返回
/// <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;
}
完整的脚本长这样
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设置为0if (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
}
使用时,类似这样
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;}
}
演示
其他扩展功能,只需要对字符串进行编辑即可
相关文章:

Unity TMP Inputfield 输入框 框选 富文本 获取真实定位
一、带富文本标签的框选是什么 UGUI的InputField提供了selectionAnchorPosition和selectionFocusPosition,开始选择时的光标下标和当前光标下标 对于未添加富文本标签时,直接通过以上两个值,判断一下框选方向(前向后/后向前&…...

如何在原生项目中集成flutter
两个前提条件: 从flutter v1.17版本开始,flutter module仅支持AndroidX的应用在release模式下flutter仅支持一下架构:x84_64、armeabi-v7a、arm6f4-v8a,不支持mips和x86;所以引入flutter前需要在app/build.gradle下配置flutter支持的架构 a…...

【设计模式】策略模式
目录 什么是策略模式 代码实现 什么是策略模式 策略模式是一种行为型设计模式,它定义了一系列算法,将每个算法封装成一个独立的对象,使得它们可以相互替换。 在策略模式中,通常有三个角色: 环境类(Cont…...

Java面试八股之Iterator和ListIterator的区别是什么
Iterator和ListIterator的区别是什么 这道题也是考查我们对迭代器相关的接口的了解程度,从代码中我们可以看出后者是前者的子接口,在此基础上做了一些增强,并且只用于List集合类型。 定义与基本概念 Iterator: 定义:…...

服务器中毒怎么办?企业数据安全需重视
互联网企业: 广义的互联网企业是指以计算机网络技术为基础,利用网络平台提供服务并因此获得收入的企业。广义的互联网企业可以分为:基础层互联网企业、服务层互联网企业、终端层互联网企业。 狭义的互联网企业是指在互联网上注册域名,建立网…...

k8s使用harbor私有仓库镜像 —— 筑梦之路
官方文档: Secret | Kubernetes ImagePullSecrets的设置是kubernetes机制的另一亮点,习惯于直接使用Docker Pull来拉取公共镜像,但非所有容器镜像都是公开的。此外,并不是所有的镜像仓库都允许匿名拉取,也就是说需要身份认证&…...

tcp bbr pacing 的对与错
前面提到 pacing 替代 burst 是大势所趋,核心原因就是摩尔定律逐渐失效,主机带宽追平交换带宽,交换机不再能轻易吸收掉主机突发,且随着视频类流量激增,又不能以大 buffer 做带宽后备。因此,主机必须 pacing…...
MySQL学习-非事务相关的六大日志、InnoDB的三大特性以及主从复制架构
一. 六大日志 慢查询日志:记录所有执行时间超过long_query_time的查询,方便定位并优化。 # 查询当前慢查询日志状态 SHOW VARIABLES LIKE slow_query_log; #启用慢查询日志 SET GLOBAL slow_query_log ON; #设置慢查询文件位置 SET GLOBAL slow_query_log_file …...

【软件测试】MIL/HIL/PIL/SIL测试
V字型开发流程 引用文章:汽车行业V模型开发详解 V模型开发(V-Model Development)是一种广泛应用于汽车行业的系统开发方法。它以字母“V”形状的图表形式展示了开发过程中不同阶段之间的关系,从需求分析到系统整合和验证&#x…...
WebKit结构深度解析:打造高效与安全的浏览器引擎
WebKit结构深度解析:打造高效与安全的浏览器引擎 在现代网络世界中,浏览器作为连接用户与互联网信息的桥梁,其背后的技术架构至关重要。WebKit,作为当今最流行的开源浏览器引擎之一,其结构设计和功能实现对于提升浏览…...
SQLSERVER对等发布问题处理
问题1: 无法对 数据库Sast_Business 执行 删除,因为它正用于复制。 (.Net SqlClient Data Provider) 处理: USE [master]; GO EXEC sp_replicationdboption dbname NSast_Business, optname Npublish, value Nfalse; EXEC sp_replica…...
CentOS 7 中时间快了 8 小时
1.查看系统时间 1.1 timeZone显示时区 [adminlocalhost ~]$ timedatectlLocal time: Mon 2024-04-15 18:09:19 PDTUniversal time: Tue 2024-04-16 01:09:19 UTCRTC time: Tue 2024-04-16 01:09:19Time zone: America/Los_Angeles (PDT, -0700)NTP enabled: yes NTP synchro…...

itext7 pdf转图片
https://github.com/thombrink/itext7.pdfimage 新建asp.net core8项目,安装itext7和system.drawing.common 引入itext.pdfimage核心代码 imageListener下有一段不安全的代码 unsafe{for (int y 0; y < image.Height; y){byte* ptrMask (byte*)bitsMask.Scan…...
搜维尔科技:Manus Xsens Metagloves新一代手指捕捉
Manus Xsens Metagloves新一代手指捕捉 搜维尔科技:Manus Xsens Metagloves新一代手指捕捉...
Python与Redis:提升性能,确保可靠性,掌握最佳实践
在 Python 中,有多个库可用于与 Redis 数据库进行交互,其中最受欢迎的是 redis-py。这是一个 Python 客户端库,提供了与 Redis 数据库进行通信的丰富功能。 Python操作Redis操作步骤 安装 redis-py 使用 pip 安装 redis-py: p…...

GPT国内能用吗
2022年11月,Open AI发布ChatGPT,ChatGPT展现了大型语模型在自然语言处理方面的惊人进步,其生成文本的流畅度和连贯性令人印象深刻,为AI应用打开了新的可能性。 ChatGPT的出现推动了AI技术在各个领域的应用,例如&#x…...

中科亿海微-CL1656功能验证开发板
I. 引言 A. 研究背景与意义 CL1656是一款精度高、功耗低、成本低的5V单片低功耗运放,由核心互联公司研发制造,CL1656 是一个 16-bit、快速、低功耗逐次逼近型 ADC,吞吐速率高达 250 kSPS,并且内置低噪声、宽 带宽采样保持放大器。…...

学习STM32第十五天
SPI外设 一、简介 STM32F4XX内部集成硬件SPI收发电路,可以由硬件自动执行时钟生成、数据收发等功能,减轻CPU负担,可配置8位/16位数据帧,高位(最常用)/低位先行,三组SPI接口,支持DMA…...

【面试题】MySQL 事务的四大特性说一下?
事务是一个或多个 SQL 语句组成的一个执行单元,这些 SQL 语句要么全部执行成功,要么全部不执行,不会出现部分执行的情况。事务是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。 事务的主要作用是保证数…...

案例实践 | InterMat:基于长安链的材料数据发现与共享系统
案例名称:InterMat-基于区块链的材料数据发现与共享系统 ■ 建设单位 北京钢研新材科技有限公司 ■ 用户群体 材料数据上下游单位 ■ 应用成效 已建设10共识节点、50轻节点,1万注册用户 案例背景 材料是构成各种装备和工程的物质载体,…...

黑马Mybatis
Mybatis 表现层:页面展示 业务层:逻辑处理 持久层:持久数据化保存 在这里插入图片描述 Mybatis快速入门 
让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...

ios苹果系统,js 滑动屏幕、锚定无效
现象:window.addEventListener监听touch无效,划不动屏幕,但是代码逻辑都有执行到。 scrollIntoView也无效。 原因:这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作,从而会影响…...
JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案
JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停 1. 安全点(Safepoint)阻塞 现象:JVM暂停但无GC日志,日志显示No GCs detected。原因:JVM等待所有线程进入安全点(如…...

智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制
在数字化浪潮席卷全球的今天,数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具,在大规模数据获取中发挥着关键作用。然而,传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时,常出现数据质…...
Xen Server服务器释放磁盘空间
disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...

安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)
船舶制造装配管理现状:装配工作依赖人工经验,装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书,但在实际执行中,工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...

C# 表达式和运算符(求值顺序)
求值顺序 表达式可以由许多嵌套的子表达式构成。子表达式的求值顺序可以使表达式的最终值发生 变化。 例如,已知表达式3*52,依照子表达式的求值顺序,有两种可能的结果,如图9-3所示。 如果乘法先执行,结果是17。如果5…...
LCTF液晶可调谐滤波器在多光谱相机捕捉无人机目标检测中的作用
中达瑞和自2005年成立以来,一直在光谱成像领域深度钻研和发展,始终致力于研发高性能、高可靠性的光谱成像相机,为科研院校提供更优的产品和服务。在《低空背景下无人机目标的光谱特征研究及目标检测应用》这篇论文中提到中达瑞和 LCTF 作为多…...
JS红宝书笔记 - 3.3 变量
要定义变量,可以使用var操作符,后跟变量名 ES实现变量初始化,因此可以同时定义变量并设置它的值 使用var操作符定义的变量会成为包含它的函数的局部变量。 在函数内定义变量时省略var操作符,可以创建一个全局变量 如果需要定义…...