之前分享過一個通過添加元件實作漸變色的文章,但是通過元件實作有一個弊端,他隻能設定整個文本漸變,不能隻設定一段文字漸變。今天分享一個通過正則比對自定義的富文本标記來實作漸變色的方法,這樣的好處就是比較自由。
自定義的漸變色标記:<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();
}
}