接到一个需求,TextMeshPro 图文混排。
方案一:TMP自带图文混排
优点 | 布局适应优秀,字体左中右布局位置都很不错 |
缺点 | 用到的图片需要打包,使用Resources加载 |
使用方法
打包图集
打包图集这里推荐使用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;
}
}