TextMeshPro图文混排的两种实现方式,不打图集

news2024/9/20 16:10:47

TMP图文混排

    • 方案一:TMP自带图文混排
      • 使用方法
        • 打包图集
        • 使用
    • 方案二:不打图集,可以使用任何图片

接到一个需求,TextMeshPro 图文混排。

方案一:TMP自带图文混排

优点布局适应优秀,字体左中右布局位置都很不错
缺点用到的图片需要打包,使用Resources加载

使用方法

打包图集

打包图集这里推荐使用TexturePacker工具,功能强大,完美适配。这个软件也可以用于压缩图集。

TexturePacker链接

1.将你需要的散图或文件夹拖到这个区域,或使用添加精灵按钮添加。

2.选中下图按钮,选择Json(Array), 注意是Array。
下面的图片模式和打包可以自己尝试,主要是控制压缩和图片格式,这里不做介绍。

3.点击发布,获得一个Json文件和一张PNG,Json 是PNG各个图片的位置信息。

4.将上一步的资源导入Unity,然后打开TextMeshPro的图集工具 Sprite Importer。

5.将Json 和PNG 拖入下图中对应的位置。点击生成,然后存储到自己的文件夹

使用

1.将生成的图集拖入TMP的Sprite Asset中,使用图片时,在字符串中插入<sprite=你的图片id>,例如图中“<sprite=0> 图片好看吗”

2. ??? 图片咋这样??? ,发现图片大小不对,被勾选了自动转变为2的n次方。这里改成None ,图片能完整显示了

3. 还是不对,位置偏差好多,按下图2修改X和Y的偏移,最下方的Global Offset 是同时修改所有图片

完成啦,撒花~~

方案二:不打图集,可以使用任何图片

优点当你有大量图片都可能用于图文混排,不方便打包时使用。代码没多少,随便修随便改- -
缺点对布局适配一般,只支持了整个组件的对齐,文字只支持了左对齐

原理就是将含图片的文本拆分成几段文本和图片的组合,计算位置,使用多个TMP和图片拼接
图片暂定格式 [img] 图片ID[/img]

using System.Collections.Generic;
using System.Text.RegularExpressions;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class TextImageView : MonoBehaviour
{
   
    public enum TextImageAlignmentH
    {
        Left,
        Center,
        Right
    }
    public enum TextImageAlignmentV
    {
        Top,
        Center,
        Bottom
    }
    public Transform content;
    public int FontSize;
    public float maxWidth;
    public TextMeshProUGUI tmp;
    public Image img;
    public TextImageAlignmentH alignmentH = TextImageAlignmentH.Center;
    public TextImageAlignmentV alignmentV = TextImageAlignmentV.Center;
    private float _lineHeight;
    private float _averageWidth;
    private float _maxLength;
    private List<KeyValuePair<string, int>> _contentList = new List<KeyValuePair<string, int>>();
    public string text
    {
        set { Test(value); }
    }

    public void Test(string text)
    {
        _contentList = StringToKeyValuePair(text);
        var reduceWidth = 0f;
        var height = 0f;
        tmp.fontSize = FontSize;
        tmp.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, maxWidth);
        tmp.gameObject.SetActive(true);
        tmp.text = "test";
        tmp.ForceMeshUpdate();
        _lineHeight = tmp.textInfo.lineInfo[0].lineHeight;
        tmp.gameObject.SetActive(false);
        
        content.DestroyChildren();
        for (var i = 0; i < _contentList.Count; i++)
        {
            if (_contentList[i].Value == 0)
            {
                var label = TestText(_contentList[i].Key, reduceWidth,height);
                
                for (var i1 = 0; i1 < label.textInfo.lineInfo.Length; i1++)
                {
                    var lineInfo = label.textInfo.lineInfo[i1];
                    var width = lineInfo.length;
                    if(_maxLength < width)
                    {
                        _maxLength = width;
                    }
                    
                    if(i1 == label.textInfo.lineCount - 1)
                    {
                        height = (label.transform.localPosition.y - i1*_lineHeight);
                        reduceWidth = maxWidth - width - 5;
                    }
                }

            }
            else
            {
                
                var imgData = TestImage(_contentList[i].Key, reduceWidth, height);
                reduceWidth = imgData.reduce + 5;
                if (_maxLength < (maxWidth - imgData.reduce))
                {
                    _maxLength = maxWidth - imgData.reduce;
                }
                if(imgData.changeLine)
                {
                    height -= _lineHeight;
                }
            }
            
        }
        height -= _lineHeight;
        var x = 0f;
        switch (alignmentH)
        {
            case TextImageAlignmentH.Left:
                x = 0;
                break;
            case TextImageAlignmentH.Center:
                x = (maxWidth - _maxLength) / 2;
                break;
            case TextImageAlignmentH.Right:
                x = maxWidth - _maxLength;
                break;
        }
        
        var y = 0f;
        switch (alignmentV)
        {
            case TextImageAlignmentV.Top:
                y = _lineHeight;
                break;
            case TextImageAlignmentV.Center:
                y = -height/2 +_lineHeight/2;
                break;
            case TextImageAlignmentV.Bottom:
                y = -height;
                break;
        }
        content.transform.localPosition = new Vector3(x,y,0);
    }
    // <color=#FFFFFF><sprite=1/> 计分时\n<color=#FF0000>+4 </color>倍率</color>
    private List<KeyValuePair<string, int>> StringToKeyValuePair(string text)
    {
        var result = new List<KeyValuePair<string, int>>();

        // 拆分字符串,匹配图片名和文字内容
        var splitText = Regex.Split(text, @"(\[img\]|\[/img\])");

        // 遍历拆分后的字符串
        for (var i = 0; i < splitText.Length; i++)
        {
            switch (splitText[i])
            {
                case "[img]":
                {
                    // 如果是图片名,将它加入结果列表
                    result.Add(new KeyValuePair<string, int>(splitText[i + 1], 1));
                    // 跳过处理下一个字符串
                    i++;
                }
                    continue;
                case "[/img]":
                    // 如果是 [/img] ,跳过处理下一个字符串
                    continue;
                default:
                {
                    var content = splitText[i];
                    if (string.IsNullOrEmpty(content))
                    {
                        continue;
                    }
                    result.Add(new KeyValuePair<string, int>(content, 0));
                }
                    break;
            }
        }

        return result;
    }
 
    public string testValue;
    [ContextMenu("测试")]
    
    public void Test1()
    {
        // TestText(testValue, 300,0);
        Test(testValue);
    }

    private TextMeshProUGUI TestText(string value,float reduceWidth,float height)
    {
        var obj = GameObject.Instantiate((UnityEngine.Object)tmp.gameObject) as GameObject;
        //GameObject obj = null;
        if (obj == null)
        {
            return null;
        }
        obj.SetActive(true);
        if(_averageWidth == 0)
        {
            _averageWidth = obj.GetComponent<TextMeshProUGUI>().GetPreferredValues(value).x/ value.Length;
        }
        var label = obj.GetComponent<TextMeshProUGUI>();
        label.fontSize = FontSize;
        label.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, maxWidth); 
        obj.transform.SetParent(content);
        obj.transform.localScale = Vector3.one;
        if (reduceWidth <= _averageWidth)//剩下的空间不够一个字,直接换行
        {
            obj.transform.localPosition = new Vector3(0, height - _lineHeight, 0);
        }
        else
        {
          
            value = $"<space={maxWidth - reduceWidth}>{value}"; // 创建新字符串
            obj.transform.localPosition = new Vector3(0, height, 0); 
        }
      
        
        value = DelUrlFlag(value);
        
        label.text = value;
        
        label.ForceMeshUpdate();
        return label; // 计算剩余宽度
        
    }
   
    private (bool changeLine,float reduce) TestImage(string value,float reduceWidth,float height)
    {
        
        var obj = GameObject.Instantiate((UnityEngine.Object)img.gameObject) as GameObject;
        if (obj == null)
        {
            return (false,reduceWidth);
        }
        obj.SetActive(true);
        var sp = obj.GetComponent<Image>();
        sp.SetIcon(int.Parse(value));//value是图片的ID,这里替换为自己的设置图片的方法
        var tempHeight = _lineHeight ;
        sp.SetNativeSize();
        var size = sp.rectTransform.sizeDelta;
        var rate = size.y / tempHeight;
        sp.rectTransform.sizeDelta = new Vector2(size.x / rate, tempHeight);
        var needWidth = sp.rectTransform.sizeDelta.x;
        obj.transform.SetParent(content);
        obj.transform.localScale = Vector3.one;
        if (needWidth > reduceWidth)
        {
            obj.transform.localPosition = new Vector3(-maxWidth/2 +needWidth/2, height - _lineHeight, 0);
            return (true,maxWidth -needWidth);
        }
        else
        {
            obj.transform.localPosition = new Vector3(-maxWidth/2 + (maxWidth - reduceWidth)+needWidth/2, height, 0);
            return (false,reduceWidth - needWidth);
        }
        
      
    }
    private string DelUrlFlag(string strTxt)
    {
        strTxt = strTxt.Replace("[/url]", "");
        int nStartLeftBracket = 0;
        int nStartRightBracket = 0;

        while (true)
        {
            nStartLeftBracket = strTxt.IndexOf('[', nStartLeftBracket);
            nStartRightBracket = strTxt.IndexOf(']', nStartRightBracket);

            if (nStartLeftBracket > -1 && nStartRightBracket > -1 && nStartRightBracket > nStartLeftBracket)
            {
                string sub = strTxt.Substring(nStartLeftBracket, nStartRightBracket - nStartLeftBracket + 1);
                if (sub.Contains("url"))
                {
                    strTxt = strTxt.Remove(nStartLeftBracket, nStartRightBracket - nStartLeftBracket + 1);

                    nStartLeftBracket = 0;
                    nStartRightBracket = 0;
                }
                else
                {
                    nStartLeftBracket = nStartRightBracket;
                    nStartRightBracket += 1;
                }
            }
            else
            {
                break;
            }
        }

        return strTxt;
    }
}

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

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

相关文章

基于SSM的土家风景文化管理平台(有报告)。Javaee项目。ssm项目。

演示视频&#xff1a; 基于SSM的土家风景文化管理平台&#xff08;有报告&#xff09;。Javaee项目。ssm项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spri…

基于springboot的“漫画之家”系统

目录 背景 技术简介 系统简介 界面浏览 背景 随着科技的不断进步&#xff0c;计算机已经变成了人们日常生活和工作不可或缺的工具。在这样的环境下&#xff0c;互联网技术被广泛运用于各个领域&#xff0c;以提升工作和生活的效率&#xff0c;推动了网络信息技术的迅猛发展…

递增四元组

解法&#xff1a; 首先都可以想到dp[i]&#xff1a;第i个元素结尾的递增四元组有dp[i]个 然后发现有一组数据&#xff1a;2,3,6,1,5,8。会出现6结尾和5结尾的递增三元组&#xff0c;也就是未来的决策受过去影响&#xff0c;专业的说就是有后效性。需要强化约束条件&#xff0…

基于springboot+vue的游戏交易系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

Grass手机注册使用教程,利用闲置手机WiFi带宽赚钱

文章目录 Grass是什么&#xff1f; 项目介绍Grasss手机使用步骤第一步&#xff1a;下载狐猴浏览器第二步&#xff1a;注册账户&#xff08;已注册直接跳过&#xff09;第三步&#xff1a;安装Grass Chrome插件1、推荐离线安装2、在线安装 第四步&#xff1a;登录第五步&#xf…

随笔】Git -- 常用命令(四)

&#x1f48c; 所属专栏&#xff1a;【Git】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#x1f496; 欢迎大…

2核4G服务器阿里云性能测评和优惠价格表

阿里云2核4G服务器租用优惠价格&#xff0c;轻量2核4G服务器165元一年、u1服务器2核4G5M带宽199元一年、云服务器e实例30元3个月&#xff0c;活动链接 aliyunfuwuqi.com/go/aliyun 活动链接如下图&#xff1a; 阿里云2核4G服务器优惠价格 轻量应用服务器2核2G4M带宽、60GB高效…

多线程基础 -概念、创建、等待、分离、终止

文章目录 一、 线程概念1. 什么是线程2. 线程的优点3.线程的缺点4. 线程异常5. 线程用途 二、 Linux进程VS线程1. 进程和线程2. 进程和线程的地址空间3. 进程和线程的关系 三、Linux线程控制1. POSIX线程库2. 线程创建3. 线程ID及进程地址空间布局4. 线程终止5. 线程等待6. 线程…

发布 AUR 软件包 (ArchLinux)

首发日期 2024-03-09, 以下为原文内容: 理论上来说, 我们应该平等的对待每一个 GNU/Linux 发行版本. 但是, 因为窝日常使用 ArchLinux, 所以对 ArchLinux 有一些特别的优待, 比如自己做的软件优先为 ArchLinux 打包发布. 本文以软件包 librush-bin 为例, 介绍发布 AUR 软件包的…

leecode1793 | 好子数组的最大分数 | 求给高度矩阵最大值

题目我就不念了&#xff0c;就一个字难理解&#xff0c;给的题总是这么难懂&#xff0c;总感觉出题人的语文是体育老师教的&#xff1f; 还有就是思维转变&#xff0c;才能能好的理解&#xff1f;一味的钻牛角尖死理解&#xff0c;效果不好 思维的转变 >悟性&#xff1f;&am…

以RISC-V架构的CLIC中断机制讲解:中断咬尾、中断抢占、中断晚到

1、中断的相关属性 中断所属特权模式&#xff08;M模式 > S模式 > U模式&#xff09;中断等级&#xff1a;决定是否能够抢占当前的中断中断优先级&#xff1a;影响中断的仲裁&#xff0c;优先级高时优先被响应中断编号&#xff1a;区分中断&#xff0c;影响中断的仲裁 …

农业四情监测系统的工作原理

农业四情监测系统的工作原理【TH-Q1】农业四情监测系统是一种应用现代科技手段&#xff0c;以实现对农田环境信息的实时监测和数据采集的系统。这一系统通过对农田的土壤、气象、病虫害以及作物生长状况等四个方面的实时监测&#xff0c;帮助农民和农业管理者更好地了解和掌握农…

数据结构:详解【栈和队列】的实现

目录 1. 栈1.1 栈的概念及结构1.2 栈的实现1.3 栈的功能1.4 栈的功能的实现1.5 完整代码 2. 队列2.1 队列的概念及结构2.2 队列的实现2.3 队列的功能2.4 队列的功能的实现2.5 完整代码 1. 栈 1.1 栈的概念及结构 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的…

opencv自定义间隔帧获取视频转存为图片的GUI界面实现

该程序功能只将mp4转为jpg 希望得到您的指导 非常感谢您观看我的博客&#xff0c;我的博客是为了记录我的学习过程同时保留我的某些可重复利用代码以方便下次使用。如果您对我的博客有任何建议还请您不吝指出&#xff0c;非常感谢您对我的指导。 背景 在实现opencv逐帧获取…

js处理数组分类

const obj [{"groupingType": "1","remark": "梨花带雨","totalRmbMoney": 7,"kyeGroupingType": "广州一组"},{"groupingType": "2","remark": "99","…

js 实现动画的两种方案对比:setTimeout vs RAF (requestAnimationFrame)

setTimeout 需手动控制频率&#xff0c;页面隐藏后仍会执行动画&#xff0c;更加耗费性能。 requestAnimationFrame 简称 RAF , 会在浏览器中每次刷新屏幕时调用一个函数&#xff0c;用于创建平滑的动画&#xff0c;因为它会自动适应屏幕的刷新率&#xff0c;无需手动控制频率。…

搭建自己的博客-拾壹博客

写在前面 唠叨两句 作为一个技术开发人员&#xff0c;没有一个自己的博客&#xff0c;人生注定缺少点什么东西&#xff0c;是不是&#xff1f;最近研究了一些博客搭建&#xff0c;本文是使用开源项目”拾壹博客“进行搭建。 推荐等级 所需技术难度&#xff1a;4星 后续自定义…

Redis中的缓存击穿

缓存击穿 缓存击穿问题也叫热点key问题&#xff0c;就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了&#xff0c;无数的请求访问会在瞬间给数据库带来巨大压力。 &#x1f914;现象分析&#xff1a; 当线程1查询缓存时&#xff0c;未命中&#xff0c;于是从数据…

2004-2022年各省化学需氧量数据(无缺失)

2004-2022年各省化学需氧量数据&#xff08;无缺失&#xff09; 1、2004-2022年 2、范围&#xff1a;31省 3、指标&#xff1a;化学需氧量 4、来源&#xff1a;各省年鉴、国家统计局、环境年鉴 5、指标解释&#xff1a;化学需氧量(COD)排放量指工业废水中COD排放量与生活污…

python的O2O生鲜食品订购flask-django-nodejs-php

用户只能通过一些类似软件进行查看生鲜超市&#xff0c;这样的管理方式仍然是比较机械传统的&#xff0c;本文通过对市面上常见的线上管理系统与现实生活中结合问题的讨论&#xff0c;从一个微信小程序的O2O生鲜食品订购角度进行需求分析&#xff0c;提供一些新的思路&#xff…