之前分享过一个通过添加组件实现渐变色的文章,但是通过组件实现有一个弊端,他只能设置整个文本渐变,不能只设置一段文字渐变。今天分享一个通过正则匹配自定义的富文本标记来实现渐变色的方法,这样的好处就是比较自由。
自定义的渐变色标记:<gradient></gradient>
内部属性:top :顶部颜色 bottom :底部颜色 effect :阴影颜色 distance :阴影偏移
举例:<gradient top=#00FF00FF bottom=#FFFF00FF effect=#FF0000FF distance=1,-1>测试文字</gradient>测试文字<gradient top=#0000FFFF bottom=#FF8E00FF effect=#00D5FFFF distance=1,-1>测试文字</gradient>
效果图:
直接上代码:具体解释请看注释
先上UI代码
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GradientText : Text
{
protected class GradientConfig
{
public int startIndex;//标记为渐变色文字的起始顶点索引
public int endIndex;//标记为渐变色文字的结束顶点索引
public Color topColor;//顶部颜色
public Color bottomColor;//底部颜色
public bool shadow = false;//是否使用阴影
public Color effectColor;//阴影颜色
public Vector2 distance;//阴影偏移
/// <summary>
/// 构造函数
/// </summary>
/// <param name="str">带有渐变色标记的整段文字<gradient>...</gradient></param>
/// <param name="offset">这段文字的首个字符在文本中的位置</param>
/// <param name="length">被标记的文本内容的长度</param>
public GradientConfig( string str , int offset , int length )
{
startIndex = offset * 4;
endIndex = (offset + length) * 4;
str = str.Trim();//去除字符串的首尾空白字符
var paramGroups = str.Split(' ');
if (paramGroups != null && paramGroups.Length > 0 )
{
for (int i = 0; i < paramGroups.Length; i++)
{
var group = paramGroups[i].Split('=');
if(group.Length == 2)
{
SetParam(group[0], group[1]);
}
}
}
}
///设置参数
public void SetParam( string id , string value )
{
switch (id)
{
case "top":
if( !ColorUtility.TryParseHtmlString(value , out topColor ))
{
topColor = Color.black;
}
break;
case "bottom":
if( !ColorUtility.TryParseHtmlString(value , out bottomColor ))
{
bottomColor = Color.black;
}
break;
case "effect":
if( !ColorUtility.TryParseHtmlString(value , out effectColor ))
{
effectColor = Color.black;
}
shadow = true;
break;
case "distance":
var xy = value.Split(',');
if(xy.Length == 2)
{
int x, y = 0;
int.TryParse(xy[0], out x);
int.TryParse(xy[1], out y);
distance = new Vector2(x, y);
}
break;
default:
break;
}
}
}
//面板上显示的文字
protected string m_showText = string.Empty;
//渐变色配置列表
protected List<GradientConfig> m_gradientConfigs = null;
//把常量引用类型设置成静态,则只需要加载一次就常驻内存了。
//定义匹配渐变色标记的正则表达式 对于正则表达式看不懂可以先去看一下我分享的另一篇文章:正则表达式详解
protected static System.Text.RegularExpressions.Regex RegexGradient = new System.Text.RegularExpressions.Regex("<gradient(?<param>.+?)>(?<text>.+?)</gradient>");
//匹配渐变色标记头的正则
//protected static System.Text.RegularExpressions.Regex RegexGradientBegin = new System.Text.RegularExpressions.Regex("<gradient(?<param>.+?)>");
//匹配渐变色标记尾的正则
//protected static System.Text.RegularExpressions.Regex RegexGradientEnd = new System.Text.RegularExpressions.Regex("</gradient>");
protected static System.Text.StringBuilder s_textBuilder = new System.Text.StringBuilder(256);
//文字是否检查过了
protected bool m_gradientChecked = false;
//是否应用了渐变色配置
protected bool m_gradientApplied = false;
//当前正在解析中的渐变色配置索引
protected int m_gradientIndex = 0;
//当前正在使用的渐变色配置
protected GradientConfig m_curConfig;
//临时记录的文本值
private string m_tempValue = "";
public override string text
{
get
{
return m_Text;
}
set
{
if (string.IsNullOrEmpty(value))
{
if (string.IsNullOrEmpty(m_Text))
return;
m_Text = "";
m_showText = "";
SetVerticesDirty();
}
else if( m_Text != value )
{
m_Text = value;
//剔除标记,获得要显示的文字
m_showText = TryParseGradient(value);
SetVerticesDirty();
SetLayoutDirty();
}
}
}
/// <summary>
/// 解析渐变色配置数据
/// </summary>
/// <param name="str">要解析的文本</param>
/// <returns></returns>
private string TryParseGradient( string str )
{
m_gradientChecked = true;
m_gradientApplied = false;
m_tempValue = str;
//如果不支持富文本则不解析字符串
if (!supportRichText) return str;
var matches = RegexGradient.Matches(str);
if (matches.Count <= 0) return str;
if (m_gradientConfigs == null)
m_gradientConfigs = ListPool<GradientConfig>.Get();
m_gradientConfigs.Clear();
s_textBuilder.Length = 0;
int _textIndex = 0;//记录文字索引
for (int i = 0; i < matches.Count; i++)
{
//将匹配的要设置渐变色的字符串提取出来,然后设置渐变色配置
var match = matches[i];
s_textBuilder.Append(str.Substring(_textIndex, match.Index - _textIndex));
var content = match.Groups["text"].Value;
var param = match.Groups["param"].Value;
var config = new GradientConfig(param, s_textBuilder.Length, content.Length);
s_textBuilder.Append(content);
_textIndex = match.Index + match.Length;
m_gradientConfigs.Add(config);
}
//把真正要显示的文字截取出来
s_textBuilder.Append(str.Substring(_textIndex, str.Length - _textIndex));
if( m_gradientConfigs.Count > 0 )
{
m_gradientApplied = true;
}
return s_textBuilder.ToString();
}
//开始渐变 ,设置当前正在使用的配置
protected void BeginGradient( int offset = 0 )
{
m_gradientIndex += offset;
if (!m_gradientApplied || m_gradientConfigs == null || m_gradientConfigs.Count <= m_gradientIndex) return;
m_curConfig = m_gradientConfigs[m_gradientIndex];
}
/// <summary>
/// 这个顶点是否执行渐变
/// </summary>
/// <param name="vertIndex">顶点索引</param>
/// <returns></returns>
protected bool IsGradient( int vertIndex )
{
if( !m_gradientApplied || m_gradientConfigs == null || m_gradientConfigs.Count <= m_gradientIndex ||
vertIndex < m_curConfig.startIndex || vertIndex >= m_curConfig.endIndex )
{
return false;
}
return vertIndex >= m_curConfig.startIndex && vertIndex < m_curConfig.endIndex;
}
/// <summary>
/// 获得这个顶点的渐变颜色,并更新渐变色配置
/// </summary>
/// <param name="vertIndex">顶点索引</param>
/// <returns></returns>
protected Color GetGradientColor( int vertIndex)
{
int per = vertIndex % 4;
var result = per < 2 ? m_curConfig.topColor : m_curConfig.bottomColor;
//如果取到当前渐变色配置的最后一个顶点了,则把渐变色配置更新到下一个
if( vertIndex + 1 == m_curConfig.endIndex )
{
BeginGradient(1);
}
return result;
}
protected override void OnEnable()
{
base.OnEnable();
//刚启动时候检查一下文字需不需要解析
TryCheckGradient();
}
public void TryCheckGradient()
{
if (!m_gradientChecked && !string.IsNullOrEmpty(m_Text) || m_tempValue != m_Text)
{
m_showText = TryParseGradient(m_Text);
}
}
protected override void OnDestroy()
{
base.OnDestroy();
if (m_gradientConfigs != null)
{
ListPool<GradientConfig>.Release(m_gradientConfigs);
m_gradientConfigs = null;
}
}
private readonly UIVertex[] m_TempVerts = new UIVertex[4];
private readonly UIVertex[] m_TempEffectVerts = new UIVertex[4];
protected override void OnPopulateMesh( VertexHelper toFill )
{
if (font == null) return;
// We don't care if we the font Texture changes while we are doing our Update.
// The end result of cachedTextGenerator will be valid for this instance.
// Otherwise we can get issues like Case 619238.
m_DisableFontTextureRebuiltCallback = true;
Vector2 extents = rectTransform.rect.size;
var settings = GetGenerationSettings(extents);
cachedTextGenerator.PopulateWithErrors(m_showText, settings, gameObject);
// Apply the offset to the vertices
IList<UIVertex> verts = cachedTextGenerator.verts;
float unitsPerPixel = 1 / pixelsPerUnit;
//Last 4 verts are always a new line... (\n)
int vertCount = verts.Count - 4;
Vector2 roundingOffset = new Vector2(verts[0].position.x, verts[0].position.y) * unitsPerPixel;
roundingOffset = PixelAdjustPoint(roundingOffset) - roundingOffset;
toFill.Clear();
m_gradientIndex = 0;
BeginGradient();
for (int i = 0; i < vertCount; ++i)
{
int tempVertsIndex = i & 3;//获得顶点索引 0,1,2,3...
m_TempVerts[tempVertsIndex] = verts[i];
m_TempVerts[tempVertsIndex].position *= unitsPerPixel;
if (roundingOffset != Vector2.zero)
{
m_TempVerts[tempVertsIndex].position.x += roundingOffset.x;
m_TempVerts[tempVertsIndex].position.y += roundingOffset.y;
}
if( IsGradient(i) )
{
//设置顶点颜色
m_TempVerts[tempVertsIndex].color = GetGradientColor(i);
//如果显示阴影
if (m_curConfig.shadow)
{
//拷贝出一份阴影顶点
m_TempEffectVerts[tempVertsIndex] = verts[i];
//设置阴影偏移
var temp = m_TempVerts[tempVertsIndex].position;
m_TempEffectVerts[tempVertsIndex].position = new Vector3(temp.x + m_curConfig.distance.x, temp.y + m_curConfig.distance.y);
//设置阴影颜色
m_TempEffectVerts[tempVertsIndex].color = m_curConfig.effectColor;
if (tempVertsIndex == 3)
{
//将阴影顶点添加到UI顶点队列
toFill.AddUIVertexQuad(m_TempEffectVerts);
}
}
}
if (tempVertsIndex == 3)
{
toFill.AddUIVertexQuad(m_TempVerts);
}
}
m_DisableFontTextureRebuiltCallback = false;
}
[ContextMenu("拷贝模板")]
private void SetStartToCurrentValue()
{
GUIUtility.systemCopyBuffer = @"<gradient top=#00FF00FF bottom=#FFFF00FF effect=#FF0000FF distance=1,-1>测试文字</gradient>测试文字<gradient top=#0000FFFF bottom=#FF8E00FF effect=#00D5FFFF distance=1,-1>测试文字</gradient>";
text = "文字已复制到剪贴板";
}
public override float preferredWidth
{
get
{
var settings = GetGenerationSettings(Vector2.zero);
return cachedTextGeneratorForLayout.GetPreferredWidth(m_showText, settings) / pixelsPerUnit;
}
}
public override float preferredHeight
{
get
{
var settings = GetGenerationSettings(new Vector2(GetPixelAdjustedRect().size.x, 0.0f));
return cachedTextGeneratorForLayout.GetPreferredHeight(m_showText, settings) / pixelsPerUnit;
}
}
}
还有editor的代码:
using UnityEditor;
[CustomEditor(typeof(GradientText),true)]
[CanEditMultipleObjects]
public class GradientTextEditor : UnityEditor.UI.TextEditor {
private GradientText text = null;
protected new void OnEnable()
{
base.OnEnable();
text = target as GradientText;
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
//检视面本GUI刷新的时候去刷新一下文本
text.TryCheckGradient();
}
}