好久没有来更新了,甚至我的账号都掉了,试了半天才搞对密码。 最近也真的是很忙,甚至上周日差点加班,幸好…… 废话不多说,开始了!!!
废话
首先说明,这个大标题都是废话,可以跳过。
图文混排,这是一个项目中最常见的功能了。没错,我们的项目也有这个需求,幸运的是,我不会呀!要不也不会来写这片文章了。 不光不会,我甚至觉得这东西贼麻烦,想尽量往后拖。然而他们给我安排的时间实在是太多了,不弄不行了。
于是开始百度一下别人写好的东西。 看到第一种,把要需要的图片打个图集,然后用Mesh贴上去。嗯,局限性很大,根本不是我所能理解的,引用过来也不知道能不能正常运行,还要单打一个图集出来,性能也不了解,有问题我也不会改…… 然后是第二种,做一个Asset文件,把需要的图片加进去,然后自己命名,设置尺寸……个人认为还不如第一种,不过他是用来做gif效果的。但是大部分的项目不用这么麻烦的东西,还不如直接弄个Skeleton插件进来了,再套用第三种方法就行了。 最后是第三种,修改Text的OnPopulateMesh方法,在获取顶点信息的过程中,通过正则来把图片的字符串改为宽度合适的空格,并记录图片相关数据。接着在物体上挂载几个Image,把图片信息赋值进去。
第三种方式看起来不错,主要是他给的代码很全,逻辑也很容易看。然而弄着弄着,发现很多问题,特别是位置各种不对。只能默默的改,改了半天,终于看起来差不多了。
就这样平静的过了两个月,来了一个单子【在不同分别率下,图文混排位置不对】 太难受了,只能一点一点的重新看了起来,开始下断点看各个顶点信息。终于解决了一大堆问题,比如说图片占位的空格数量并不对,计算图片位置中心的顶点并不对,没有把换行和原有的空格计算等等…… 果然人是不能懒的,之前没有完全看透,现在被迫完全梳理了一下逻辑。-_-
就这样,我以为终于要结束了,然而昨天又给我提了个单子【在手机上,偶现出只有图片没有文字的情况】 这我就很苦恼了,开始各种排查,最终大概知道了,应该是OnPopulateMesh的问题,但是这该咋改…… 一直到晚上,我睡觉前也在想这个问题,突然想到,我可以把覆写text的set方法,输入的时候就把文字替换成最终的结果。这样的话在底层逻辑下触发OnPopulateMesh,这总不该有问题了。
于是今天,我来到了公司,去git上找了UGUI2019的源码,看了下text的逻辑。把覆写的OnPopulateMesh方法注掉,在text的set方法里,对m_Text直接做正则处理。试了一下,倒是可以了。 正当我高兴的时候,我发现新的问题了。因为是在set方法做的处理,所以会对m_Text的值做彻底覆盖。这就意味着,如果是直接坐在预制体里的,原来的值就完全丢失了。
emmmm,只好再改回去。看来只能看一下OnPopulateMesh方法是哪里出问题了。我就在源码里继续看OnPopulateMesh的写法。发现和我从网上找的方法有很多不同。 我明白了,因为我复制来的方法是2017的,而现在的项目是2019的,所以底层逻辑是不同的。应该就是这个问题了。 但是新的担忧又来了,因为git上的是2019.1.05的,而我的项目是2019.4的,我不能确保代码是有效的。而且万一后面又要用2018或2020甚至2025那? 我只好重新梳理一下逻辑,发现OnPopulateMesh方法里调用了text,也就是说OnPopulateMesh覆写的核心就是获取将text修改为正确的即可。那我岂不是可以在text的get方法里做计算修改。 试了一下,注掉OnPopulateMesh方法,覆写text的get方法。 就成功了,好家伙,原来只需要改源码就行了。
废话真的结束了,开始搞~
正文
这里才是真的代码逻辑部分
首先说一下这个做法的逻辑。 1.当输入文本的时候,会触发SetVerticesDirty和SetLayoutDirty两个方法,通知底层当前的渲染信息脏了,然后就会主动触发OnPopulateMesh。 2.修改text的get方法,将本来应该返回的m_Text重新修正。但是不要修改m_Text自身,防止污染自身数据。 3.修正的逻辑就是先用正则将<icon=XXX>的部分移除,将其替换为合适数量的空格。再将图片的属性录入到列表中。 4.创建对应数量的Image,通过图片的属性修改显示。
然后说一下图文混排文本的写法
This script can show <icon name=xxx size=40 x=0 y=0/> and string.
脚本的私有变量
public class MixedPicText : Text
{
private static readonly string replaceStr = "\u00A0";
private static readonly Regex imageTagRegex = new Regex(@"<icon name=([^>\s]+)([^>]*)/>");
private static readonly Regex imageParaRegex = new Regex(@"(\w+)=([^\s]+)");
private List<RichTextImageInfo> imageInfoList = new List<RichTextImageInfo>();
private bool isImageDirty = false;
private IList<UIVertex> verts = null;
private List<RectTransform> showImageList = new List<RectTransform>();
}
图片的属性类
public class MixedPicTextImageInfo
{
public string name;
public Vector2 position;
public int startVertex;
public int vertexLength;
public int size = 40;
public float offsetX = 0f;
public float offsetY = 0f;
public void SetValue(string key, string value)
{
switch (key)
{
case "size":
{
int.TryParse(value, out size);
break;
}
case "x":
{
float.TryParse(value, out offsetX);
break;
}
case "y":
{
float.TryParse(value, out offsetY);
break;
}
default:
break;
}
}
}
那么就来说一下拆解的方法
protected string CalculateLayoutWithImage(string richText)
{
Vector2 extents = rectTransform.rect.size;
var settings = GetGenerationSettings(extents);
float unitsPerPixel = 1 / pixelsPerUnit;
float spaceWidth = cachedTextGenerator.GetPreferredWidth(replaceStr, settings) * unitsPerPixel;
imageInfoList.Clear();
Match match = null;
StringBuilder builder = new StringBuilder();
while ((match = imageTagRegex.Match(richText)).Success)
{
MixedPicTextImageInfo imageInfo = new MixedPicTextImageInfo();
imageInfo.name = match.Groups[1].Value;
string paras = match.Groups[2].Value;
if (!string.IsNullOrEmpty(paras))
{
var keyValueCollection = imageParaRegex.Matches(paras);
for (int i = 0; i < keyValueCollection.Count; i++)
{
string key = keyValueCollection[i].Groups[1].Value;
string value = keyValueCollection[i].Groups[2].Value;
imageInfo.SetValue(key, value);
}
}
imageInfo.startVertex = match.Index * 4;
int num = Mathf.CeilToInt(imageInfo.size / spaceWidth) + 1;
imageInfo.vertexLength = num * 4;
imageInfoList.Add(imageInfo);
builder.Length = 0;
builder.Append(richText, 0, match.Index);
for (int i = 0; i < num; i++)
{
builder.Append(replaceStr);
}
builder.Append(richText, match.Index + match.Length, richText.Length - match.Index - match.Length);
richText = builder.ToString();
}
cachedTextGenerator.Populate(richText, settings);
verts = cachedTextGenerator.verts;
int vertCount = verts.Count;
for (int i = imageInfoList.Count - 1; i >= 0; i--)
{
MixedPicTextImageInfo imageInfo = imageInfoList[i];
int charIndex = imageInfo.startVertex / 4;
string str = richText.Substring(0, charIndex);
int newLine = str.Split('\n').Length - 1;
int whiteSpace = str.Split(' ').Length - 1;
int indexOfTextQuad = (charIndex * 4) - newLine * 4 - whiteSpace * 4;
if (indexOfTextQuad < vertCount) {
Vector2 pos = (verts[indexOfTextQuad].position +
verts[indexOfTextQuad + 1 + imageInfo.vertexLength - 4].position +
verts[indexOfTextQuad + 2 + imageInfo.vertexLength - 4].position +
verts[indexOfTextQuad + 3].position) / 4f;
float posScale = 1f;
#if UNITY_EDITOR
if (Application.isPlaying)
{
---
posScale = UICanvas.Get().canvas.scaleFactor;
---
}
else
{
if (canvas != null)
{
posScale = canvas.scaleFactor;
}
}
#else
posScale = UICanvas.Get().canvas.scaleFactor;
#endif
pos /= posScale;
pos += new Vector2(imageInfo.offsetX + spaceWidth / 2, imageInfo.size * 0.3f + imageInfo.offsetY);
imageInfo.position = pos;
} else {
imageInfoList.RemoveAt(i);
}
}
isImageDirty = true;
return richText;
}
修正的字符串的方法好了,那么就可以开始实装了。
public override string text {
get
{
return CalculateLayoutWithImage(m_Text);
}
set => base.text = value;
}
接着来说一下生成图片。原文章里是放在Update里,我不知道为什么,也没有试一下放在别的地方,所以就先保持原样。
protected void Update()
{
if (isImageDirty)
{
isImageDirty = false;
RectTransform imgTrans;
Image imageComp;
for (int i = 0; i < imageInfoList.Count; i++)
{
if (i < showImageList.Count)
{
imgTrans = showImageList[i];
imgTrans.gameObject.SetActive(true);
imageComp = imgTrans.GetComponent<Image>();
}
else
{
imgTrans = new GameObject("Image", typeof(RectTransform)).transform as RectTransform;
imgTrans.SetParent(transform);
imageComp = imgTrans.gameObject.AddComponent<Image>();
showImageList.Add(imgTrans);
}
MixedPicTextImageInfo imageInfo = imageInfoList[i];
imgTrans.localScale = Vector3.one;
---
imageComp.sprite = sprite;
---
imageComp.SetNativeSize();
imageComp.raycastTarget = false;
imgTrans.localScale = Vector3.one * imageInfo.size / imgTrans.rect.width;
imgTrans.anchoredPosition = imageInfo.position;
}
for (int i = imageInfoList.Count; i < showImageList.Count; i++)
{
showImageList[i].gameObject.SetActive(false);
}
}
}
}
最后还有两个覆写方法
protected override void Awake()
{
base.Awake();
showImageList.Clear();
for (int i = transform.childCount - 1; i >= 0; i--)
{
#if UNITY_EDITOR
if (Application.isPlaying)
{
#endif
Destroy(transform.GetChild(i).gameObject);
#if UNITY_EDITOR
}
else
{
DestroyImmediate(transform.GetChild(i).gameObject);
}
#endif
}
}
protected override void Start()
{
base.Start();
OnPopulateMesh(new VertexHelper());
}
结语
这个只是昨天修改的,实际上还会不会有别的问题,还要再验。 如果有新的问题,我会再来改的。
|