콘텐츠로 이동

UGUI Text에 수평 줄임표(...) 직접 구현하기

텍스트 길이가 텍스트 영역보다 길면 줄임표(ellipsis) 처리가 필요하다.

TextMeshProUGUI는 Overflow 옵션으로 Ellipsis를 제공하지만, 기본 클래스인 Text에는 이 기능이 없다. Text 클래스의 Overflow는 다음 기능만 제공한다.

  • Horizontal(가로 영역): Wrap(다음 행으로 개행), Overflow(영역 초과 허용)
  • Vertical(세로 영역): Truncate(영역 초과 자르기), Overflow(영역 초과 허용)

Best Fit(글자 크기를 자동으로 줄여 주는 기능)도 있지만, 수평 줄임표(…)를 써야만 하는 경우가 있다. 또 글자 수가 같은 두 문자열이라도 글자마다 폭이 달라 실제 가로 길이는 다를 수 있으므로, 글자 수로 자르는 방식은 맞지 않는다.

Text 클래스를 상속한 EllipsisUGUIText를 만들어 사용한다.

  • 수평 줄임표(…)는 유니코드 8230이므로 char 타입으로 명시적으로 캐스팅한다.
  • UGUI Text 클래스는 preferredWidth 프로퍼티로 실제 글자의 가로 길이를 제공한다.
  • 이를 이용해 UpdateGenerateText()에서 RectTransform의 rect.size.x와 비교하고, 영역보다 길면 while을 돌며 끝 글자를 하나씩 잘라 낸 뒤 줄임표를 붙인다.
  • OnPopulateMesh()는 텍스트 mesh의 정점을 직접 채울 때 오버라이드하는 메서드다.
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
using System.Text;
[ExecuteAlways]
public class EllipsisUGUIText : Text
{
private readonly StringBuilder updatedStringBuilder = new StringBuilder(50);
private const char ellipsis = (char)8230;
private string cachedText = "";
public override string text
{
get
{
return cachedText;
}
set
{
if (string.IsNullOrEmpty(value))
{
if (string.IsNullOrEmpty(cachedText))
return;
cachedText = "";
UpdateGenerateText();
SetVerticesDirty();
}
else if (cachedText != value)
{
cachedText = value;
UpdateGenerateText();
SetVerticesDirty();
SetLayoutDirty();
}
}
}
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
if (text != m_Text)
text = m_Text;
}
#endif
private void UpdateGenerateText()
{
m_Text = cachedText;
Vector2 extents = rectTransform.rect.size;
extents.x = extents.x < 0 ? -extents.x : extents.x;
if (extents.x / preferredWidth < 1)
{
float tempPrefferedWidth = preferredWidth;
var settings = GetGenerationSettings(Vector2.zero);
string tempString = m_Text;
while (extents.x / tempPrefferedWidth < 1)
{
tempString = tempString.Substring(0, tempString.Length - 1);
tempPrefferedWidth = cachedTextGeneratorForLayout.GetPreferredWidth(tempString + ellipsis, settings) / pixelsPerUnit;
}
updatedStringBuilder.Length = 0;
updatedStringBuilder.Append(tempString);
updatedStringBuilder.Append(ellipsis);
m_Text = updatedStringBuilder.ToString();
updatedStringBuilder.Length = 0;
}
}
readonly UIVertex[] m_TempVerts = 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.Populate(m_Text, settings);
Rect inputRect = rectTransform.rect;
// get the text alignment anchor point for the text in local space
Vector2 textAnchorPivot = GetTextAnchorPivot(base.alignment);
Vector2 refPoint = Vector2.zero;
refPoint.x = (textAnchorPivot.x == 1 ? inputRect.xMax : inputRect.xMin);
refPoint.y = (textAnchorPivot.y == 0 ? inputRect.yMin : inputRect.yMax);
// Determine fraction of pixel to offset text mesh.
Vector2 roundingOffset = PixelAdjustPoint(refPoint) - refPoint;
// Apply the offset to the vertices
IList<UIVertex> verts = cachedTextGenerator.verts;
float unitsPerPixel = 1 / pixelsPerUnit;
//Last 4 verts are always a new line...
int vertCount = verts.Count;// - 4;
toFill.Clear();
if (roundingOffset != Vector2.zero)
{
for (int i = 0; i < vertCount; ++i)
{
int tempVertsIndex = i & 3;
m_TempVerts[tempVertsIndex] = verts[i];
m_TempVerts[tempVertsIndex].position *= unitsPerPixel;
m_TempVerts[tempVertsIndex].position.x += roundingOffset.x;
m_TempVerts[tempVertsIndex].position.y += roundingOffset.y;
if (tempVertsIndex == 3)
toFill.AddUIVertexQuad(m_TempVerts);
}
}
else
{
for (int i = 0; i < vertCount; ++i)
{
int tempVertsIndex = i & 3;
m_TempVerts[tempVertsIndex] = verts[i];
m_TempVerts[tempVertsIndex].position *= unitsPerPixel;
if (tempVertsIndex == 3)
toFill.AddUIVertexQuad(m_TempVerts);
}
}
m_DisableFontTextureRebuiltCallback = false;
}
}
  • [ExecuteAlways]: Play Mode, Edit Mode와 상관없이 항상 실행된다. (Unity 문서)
  • OnValidate: MonoBehaviour 생명주기 중 하나로, 인스펙터에서 스크립트의 값이 변경될 때 호출된다. (Unity 문서)