深入U(xiǎn)GUI源碼去認(rèn)識(shí)Image辜梳。
作為Graphic家族最重要的成員之一,我相信你的UI里面Image是必不可少的元素仅醇。我也相信大部分使用者都能夠熟練的應(yīng)用Image冗美。這一篇文章并不是Image的使用教程魔种,只是從源碼角度的對(duì)Image的剖析析二,以及總結(jié)(源碼請(qǐng)自行下載)。
屬性
首先簡(jiǎn)單介紹一下image面板上各屬性:
Source Image:圖片資源,支持精靈貼圖叶摄;
Color:圖片顏色属韧,默認(rèn)為白色;
Material:材質(zhì)蛤吓;
Raycast Target:是否是射線投射目標(biāo)宵喂;是——此Image可以接受射線投射,并且會(huì)遮擋被覆蓋UI的事件調(diào)用会傲;否——射線忽視Image锅棕,可以穿透Image。
建議:普通Image選擇否淌山,需要添加事件調(diào)用的Image選擇是裸燎。
Image Type:圖片顯示方式,總共有4種:Simple泼疑,Sliced德绿,Tiled,F(xiàn)illed(本文的介紹重點(diǎn)退渗,此處不在解釋)移稳。
Preserve Aspect:圖片是否以原比例顯示;
Set Native Size:設(shè)置圖片以原尺寸顯示会油。
源碼剖析
接下來主要通過Image幾個(gè)重要的函數(shù)來解讀Image:
1. private Vector4 GetDrawingDimensions(bool shouldPreserveAspect);
返回的向量就是圖片去掉padding之后中間部分的x,y,z,w坐標(biāo)个粱。源碼如下:
/// Image's dimensions used for drawing. X = left, Y = bottom, Z = right, W = top.
///shouldPreserveAspect為是否按精靈的原比例顯示
///rect (x,y是左下角相對(duì)于中心點(diǎn)的坐標(biāo),weight和height分別為寬翻翩,高
private Vector4 GetDrawingDimensions(bool shouldPreserveAspect)
{
//當(dāng)前精靈的填充內(nèi)邊框(left,bottom,right,top)几蜻,一般情況下都是(0,0体斩,0梭稚,0)
var padding = activeSprite == null ? Vector4.zero : Sprites.DataUtility.GetPadding(activeSprite);
//當(dāng)前精靈的大小(包含了邊框的大行醭场)
var size = activeSprite == null ? Vector2.zero : new Vector2(activeSprite.rect.width, activeSprite.rect.height);
//目標(biāo)繪制區(qū)域的坐標(biāo)及大谢】尽(x,y為該UI相對(duì)于軸心的坐標(biāo),width蹬敲,height為UI寬高)
Rect r = GetPixelAdjustedRect();
int spriteW = Mathf.RoundToInt(size.x);
int spriteH = Mathf.RoundToInt(size.y);
//計(jì)算出一種比率暇昂,為顯示出來的圖片剔除內(nèi)邊框做準(zhǔn)備
var v = new Vector4(
padding.x / spriteW,
padding.y / spriteH,
(spriteW - padding.z) / spriteW,
(spriteH - padding.w) / spriteH);
if (shouldPreserveAspect && size.sqrMagnitude > 0.0f)
{
//原圖寬高比,目標(biāo)繪制區(qū)域?qū)捀弑? var spriteRatio = size.x / size.y;
var rectRatio = r.width / r.height;
//原圖更寬則按寬調(diào)整高度大小伴嗡,以及重新計(jì)算坐標(biāo)位置急波,反之則按高度調(diào)整
if (spriteRatio > rectRatio)
{
var oldHeight = r.height;
r.height = r.width * (1.0f / spriteRatio);
r.y += (oldHeight - r.height) * rectTransform.pivot.y;
}
else
{
var oldWidth = r.width;
r.width = r.height * spriteRatio;
r.x += (oldWidth - r.width) * rectTransform.pivot.x;
}
}
//重新計(jì)算x,y,z,w的大小
v = new Vector4(
r.x + r.width * v.x,
r.y + r.height * v.y,
r.x + r.width * v.z,
r.y + r.height * v.w
);
return v;
}
計(jì)算最后的向量如圖所示:
2. private void GenerateSimpleSprite(VertexHelper vh, bool lPreserveAspect);
簡(jiǎn)單模式下的頂點(diǎn)信息瘪校。源碼如下:
void GenerateSimpleSprite(VertexHelper vh, bool lPreserveAspect)
{
//獲得圖片的位置信息
Vector4 v = GetDrawingDimensions(lPreserveAspect);
//獲得精靈的uv坐標(biāo)
var uv = (activeSprite != null) ? Sprites.DataUtility.GetOuterUV(activeSprite) : Vector4.zero;
var color32 = color;
vh.Clear();
//添加頂點(diǎn)
vh.AddVert(new Vector3(v.x, v.y), color32, new Vector2(uv.x, uv.y));
vh.AddVert(new Vector3(v.x, v.w), color32, new Vector2(uv.x, uv.w));
vh.AddVert(new Vector3(v.z, v.w), color32, new Vector2(uv.z, uv.w));
vh.AddVert(new Vector3(v.z, v.y), color32, new Vector2(uv.z, uv.y));
//添加三角形
vh.AddTriangle(0, 1, 2);
vh.AddTriangle(2, 3, 0);
}
可以看得出來澄暮,網(wǎng)格是由4個(gè)頂點(diǎn)名段,兩個(gè)三角形構(gòu)成。在Unity中如圖所示:
在這個(gè)模式下泣懊,無論圖片怎么變化伸辟,網(wǎng)格永遠(yuǎn)是由4個(gè)頂?shù)祝瑑蓚€(gè)三角形構(gòu)成馍刮。這種模式也是最少消耗性能的模式信夫。但是帶來的問題是,如果圖形需要非等比例縮放卡啰,那么就會(huì)引起圖片顯示比例失調(diào)而失真静稻。
3. private void GenerateSlicedSprite(VertexHelper toFill)
裁剪模式下的頂點(diǎn)信息。源碼如下:
static readonly Vector2[] s_VertScratch = new Vector2[4];
static readonly Vector2[] s_UVScratch = new Vector2[4];
/// <summary>
/// 得到9個(gè)區(qū)域(用邊框裁剪開的9個(gè)區(qū)域)
/// </summary>
/// <param name="toFill"></param>
private void GenerateSlicedSprite(VertexHelper toFill)
{
//如果沒有邊框則跟普通精靈頂點(diǎn)三角形是一樣的
if (!hasBorder)
{
GenerateSimpleSprite(toFill, false);
return;
}
Vector4 outer, inner, padding, border;
if (activeSprite != null)
{
outer = Sprites.DataUtility.GetOuterUV(activeSprite);
inner = Sprites.DataUtility.GetInnerUV(activeSprite);
padding = Sprites.DataUtility.GetPadding(activeSprite);
border = activeSprite.border;
}
else
{
outer = Vector4.zero;
inner = Vector4.zero;
padding = Vector4.zero;
border = Vector4.zero;
}
Rect rect = GetPixelAdjustedRect();
//調(diào)整后的邊框大小
Vector4 adjustedBorders = GetAdjustedBorders(border / pixelsPerUnit, rect);
padding = padding / pixelsPerUnit;
//圖片的真實(shí)坐標(biāo)和大小
s_VertScratch[0] = new Vector2(padding.x, padding.y);
s_VertScratch[3] = new Vector2(rect.width - padding.z, rect.height - padding.w);
s_VertScratch[1].x = adjustedBorders.x;
s_VertScratch[1].y = adjustedBorders.y;
s_VertScratch[2].x = rect.width - adjustedBorders.z;
s_VertScratch[2].y = rect.height - adjustedBorders.w;
for (int i = 0; i < 4; ++i)
{
s_VertScratch[i].x += rect.x;
s_VertScratch[i].y += rect.y;
}
s_UVScratch[0] = new Vector2(outer.x, outer.y);
s_UVScratch[1] = new Vector2(inner.x, inner.y);
s_UVScratch[2] = new Vector2(inner.z, inner.w);
s_UVScratch[3] = new Vector2(outer.z, outer.w);
toFill.Clear();
//生成9個(gè)矩形區(qū)域
for (int x = 0; x < 3; ++x)
{
int x2 = x + 1;
for (int y = 0; y < 3; ++y)
{
if (!m_FillCenter && x == 1 && y == 1)
continue;
int y2 = y + 1;
AddQuad(toFill,
new Vector2(s_VertScratch[x].x, s_VertScratch[y].y),
new Vector2(s_VertScratch[x2].x, s_VertScratch[y2].y),
color,
new Vector2(s_UVScratch[x].x, s_UVScratch[y].y),
new Vector2(s_UVScratch[x2].x, s_UVScratch[y2].y));
}
}
}
假設(shè)上下左右都存在外邊框的話(即九宮格的圖片格式)匈辱,那么頂點(diǎn)的個(gè)數(shù)是固定的16個(gè)姊扔,如果去除中心的話,三角形是16個(gè)梅誓,帶有中心的話恰梢,三角形是18個(gè)。從源碼可以看出梗掰,網(wǎng)格的邊框4個(gè)角部是永遠(yuǎn)不會(huì)被拉伸嵌言,一直是邊框大小原比例顯示(除非是顯示的尺寸小于邊框的大小)及穗,邊框的中部摧茴,以及圖片去除邊框的中心是會(huì)隨著圖形的拉伸而變化。如圖所示:
接下來埂陆,把目光放在這張圖片的圓角上:
上圖為簡(jiǎn)單模式下的圖片拉伸
上圖為裁剪模式下的圖片拉伸苛白,你覺得那個(gè)更具原生態(tài)。而且我們也可以使用三宮格焚虱,以及更特殊的裁剪邊框的模式來處理特殊的圖片购裙。
4. void GenerateTiledSprite(VertexHelper toFill);
平鋪模式下的頂點(diǎn)信息鹃栽。源碼過長(zhǎng)就不放了躏率,其構(gòu)造方式與裁剪模式相似,不過對(duì)于平鋪模式下民鼓,除了網(wǎng)格邊框的4個(gè)與裁剪模式一樣薇芝,他的邊框中部以及圖像中間會(huì)按尺寸比例像瓦片一樣平鋪產(chǎn)生,因此會(huì)構(gòu)造大量的頂點(diǎn)和三角形丰嘉,因此這種情況除非是特殊需求夯到,盡量不要使用。如圖所示:
5. void GenerateFilledSprite(VertexHelper toFill, bool preserveAspect)
覆蓋模式的頂點(diǎn)信息饮亏。其內(nèi)部通過FillAmount的值來控制需要構(gòu)建的頂點(diǎn)的數(shù)量耍贾。這種模式對(duì)于處理進(jìn)度類似的效果非常有效阅爽。除此之外他與簡(jiǎn)單模式具有一樣的特點(diǎn)。如圖所示:
小結(jié)
作為UGUI最重要的控件之一:Image逼争,我們不僅僅要會(huì)使用优床,還要懂得他背后的原理劝赔。Image這幾種模式誓焦,各有各的特點(diǎn),比如icon我們更偏向使用簡(jiǎn)單模式,并按比例顯示着帽,對(duì)于需要拉伸的圖片杂伟,我們往往會(huì)使用裁剪模式。很多時(shí)間仍翰,我們的項(xiàng)目中需要顯示的是一張?jiān)鷳B(tài)的圖片赫粥,那么如果使我們的圖片顯得更加自然,相信在文中予借,你能找到答案越平。關(guān)于頂點(diǎn)這一塊有什么不懂的地方,請(qǐng)參考之前文章:Unity_UGUI|通向UGUI源碼的入口VertexHelper 鏈接:http://www.reibang.com/p/2245969a9173