iOS-Core-Animation-Advanced-Techniques(四:視覺(jué)效果)

本文轉(zhuǎn)載自:http://www.cocoachina.com/ios/20150104/10816.html? 為了防止cocochina以后刪除該文章,故轉(zhuǎn)載至此;

四)視覺(jué)效果

嗯,園和橢圓還不錯(cuò),但如果是帶圓角的矩形呢?

我們現(xiàn)在能做到那樣了么?

史蒂芬·喬布斯

我們?cè)诘谌隆簣D層幾何學(xué)』中討論了圖層的frame,第二章『寄宿圖』則討論了圖層的寄宿圖。但是圖層不僅僅可以是圖片或是顏色的容器;還有一系列內(nèi)建的特性使得創(chuàng)造美麗優(yōu)雅的令人深刻的界面元素成為可能。在這一章,我們將會(huì)探索一些能夠通過(guò)使用CALayer屬性實(shí)現(xiàn)的視覺(jué)效果。

圓角

圓角矩形是iOS的一個(gè)標(biāo)志性審美特性。這在iOS的每一個(gè)地方都得到了體現(xiàn),不論是主屏幕圖標(biāo),還是警告彈框,甚至是文本框。按照這流行程度,你可能會(huì)認(rèn)為一定有不借助Photoshop就能輕易創(chuàng)建圓角舉行的方法。恭喜你,猜對(duì)了。

CALayer有一個(gè)叫做conrnerRadius的屬性控制著圖層角的曲率。它是一個(gè)浮點(diǎn)數(shù),默認(rèn)為0(為0的時(shí)候就是直角),但是你可以把它設(shè)置成任意值。默認(rèn)情況下,這個(gè)曲率值只影響背景顏色而不影響背景圖片或是子圖層。不過(guò),如果把masksToBounds設(shè)置成YES的話,圖層里面的所有東西都會(huì)被截取。

我們可以通過(guò)一個(gè)簡(jiǎn)單的項(xiàng)目來(lái)演示這個(gè)效果。在Interface Builder中,我們放置一些視圖,他們有一些子視圖。而且這些子視圖有一些超出了邊界(如圖4.1)。你可能無(wú)法看到他們超出了邊界,因?yàn)樵诰庉嫿缑娴臅r(shí)候,超出的部分總是被Interface Builder裁切掉了。不過(guò),你相信我就好了 :)

圖4.1 兩個(gè)白色的大視圖,他們都包含了小一些的紅色視圖。

然后在代碼中,我們?cè)O(shè)置角的半徑為20個(gè)點(diǎn),并裁剪掉第一個(gè)視圖的超出部分(見(jiàn)清單4.1)。技術(shù)上來(lái)說(shuō),這些屬性都可以在Interface Builder的探測(cè)板中分別通過(guò)『用戶定義運(yùn)行時(shí)屬性』和勾選『裁剪子視圖』(Clip Subviews)選擇框來(lái)直接設(shè)置屬性的值。不過(guò),在這個(gè)示例中,代碼能夠表示得更清楚。圖4.2是運(yùn)行代碼的結(jié)果

清單4.1 設(shè)置cornerRadius和masksToBounds

@interface?ViewController?()

@property?(nonatomic,?weak)?IBOutlet?UIView?*layerView1;

@property?(nonatomic,?weak)?IBOutlet?UIView?*layerView2;

@end

@implementation?ViewController

-?(void)viewDidLoad

{

[superviewDidLoad];

//set?the?corner?radius?on?our?layers

self.layerView1.layer.cornerRadius?=?20.0f;

self.layerView2.layer.cornerRadius?=?20.0f;

//enable?clipping?on?the?second?layer

self.layerView2.layer.masksToBounds?=?YES;

}

@end

右圖中,紅色的子視圖沿角半徑被裁剪了

如你所見(jiàn),右邊的子視圖沿邊界被裁剪了。

單獨(dú)控制每個(gè)層的圓角曲率也不是不可能的。如果想創(chuàng)建有些圓角有些直角的圖層或視圖時(shí),你可能需要一些不同的方法。比如使用一個(gè)圖層蒙板(本章稍后會(huì)講到)或者是CAShapeLayer(見(jiàn)第六章『專用圖層』)。

圖層邊框

CALayer另外兩個(gè)非常有用屬性就是borderWidth和borderColor。二者共同定義了圖層邊的繪制樣式。這條線(也被稱作stroke)沿著圖層的bounds繪制,同時(shí)也包含圖層的角。

borderWidth是以點(diǎn)為單位的定義邊框粗細(xì)的浮點(diǎn)數(shù),默認(rèn)為0.borderColor定義了邊框的顏色,默認(rèn)為黑色。

borderColor是CGColorRef類型,而不是UIColor,所以它不是Cocoa的內(nèi)置對(duì)象。不過(guò)呢,你肯定也清楚圖層引用了borderColor,雖然屬性聲明并不能證明這一點(diǎn)。CGColorRef在引用/釋放時(shí)候的行為表現(xiàn)得與NSObject極其相似。但是Objective-C語(yǔ)法并不支持這一做法,所以CGColorRef屬性即便是強(qiáng)引用也只能通過(guò)assign關(guān)鍵字來(lái)聲明。

邊框是繪制在圖層邊界里面的,而且在所有子內(nèi)容之前,也在子圖層之前。如果我們?cè)谥暗氖纠校ㄇ鍐?.2)加入圖層的邊框,你就能看到到底是怎么一回事了(如圖4.3).

清單4.2 加上邊框

@implementation?ViewController

-?(void)viewDidLoad

{

[superviewDidLoad];

//set?the?corner?radius?on?our?layers

self.layerView1.layer.cornerRadius?=?20.0f;

self.layerView2.layer.cornerRadius?=?20.0f;

//add?a?border?to?our?layers

self.layerView1.layer.borderWidth?=?5.0f;

self.layerView2.layer.borderWidth?=?5.0f;

//enable?clipping?on?the?second?layer

self.layerView2.layer.masksToBounds?=?YES;

}

@end

圖4.3 給圖層增加一個(gè)邊框

仔細(xì)觀察會(huì)發(fā)現(xiàn)邊框并不會(huì)把寄宿圖或子圖層的形狀計(jì)算進(jìn)來(lái),如果圖層的子圖層超過(guò)了邊界,或者是寄宿圖在透明區(qū)域有一個(gè)透明蒙板,邊框仍然會(huì)沿著圖層的邊界繪制出來(lái)(如圖4.4).

圖4.4 邊框是跟隨圖層的邊界變化的,而不是圖層里面的內(nèi)容

陰影

iOS的另一個(gè)常見(jiàn)特性呢,就是陰影。陰影往往可以達(dá)到圖層深度暗示的效果。也能夠用來(lái)強(qiáng)調(diào)正在顯示的圖層和優(yōu)先級(jí)(比如說(shuō)一個(gè)在其他視圖之前的彈出框),不過(guò)有時(shí)候他們只是單純的裝飾目的。

給shadowOpacity屬性一個(gè)大于默認(rèn)值(也就是0)的值,陰影就可以顯示在任意圖層之下。shadowOpacity是一個(gè)必須在0.0(不可見(jiàn))和1.0(完全不透明)之間的浮點(diǎn)數(shù)。如果設(shè)置為1.0,將會(huì)顯示一個(gè)有輕微模糊的黑色陰影稍微在圖層之上。若要改動(dòng)陰影的表現(xiàn),你可以使用CALayer的另外三個(gè)屬性:shadowColor,shadowOffset和shadowRadius。

顯而易見(jiàn),shadowColor屬性控制著陰影的顏色,和borderColor和backgroundColor一樣,它的類型也是CGColorRef。陰影默認(rèn)是黑色,大多數(shù)時(shí)候你需要的陰影也是黑色的(其他顏色的陰影看起來(lái)是不是有一點(diǎn)點(diǎn)奇怪。。)。

shadowOffset屬性控制著陰影的方向和距離。它是一個(gè)CGSize的值,寬度控制這陰影橫向的位移,高度控制著縱向的位移。shadowOffset的默認(rèn)值是 {0, -3},意即陰影相對(duì)于Y軸有3個(gè)點(diǎn)的向上位移。

為什么要默認(rèn)向上的陰影呢?盡管Core Animation是從圖層套裝演變而來(lái)(可以認(rèn)為是為iOS創(chuàng)建的私有動(dòng)畫框架),但是呢,它卻是在Mac OS上面世的,前面有提到,二者的Y軸是顛倒的。這就導(dǎo)致了默認(rèn)的3個(gè)點(diǎn)位移的陰影是向上的。在Mac上,shadowOffset的默認(rèn)值是陰影向下的,這樣你就能理解為什么iOS上的陰影方向是向上的了(如圖4.5).

圖4.5 在iOS(左)和Mac OS(右)上shadowOffset的表現(xiàn)。

蘋果更傾向于用戶界面的陰影應(yīng)該是垂直向下的,所以在iOS把陰影寬度設(shè)為0,然后高度設(shè)為一個(gè)正值不失為一個(gè)做法。

shadowRadius屬性控制著陰影的模糊度,當(dāng)它的值是0的時(shí)候,陰影就和視圖一樣有一個(gè)非常確定的邊界線。當(dāng)值越來(lái)越大的時(shí)候,邊界線看上去就會(huì)越來(lái)越模糊和自然。蘋果自家的應(yīng)用設(shè)計(jì)更偏向于自然的陰影,所以一個(gè)非零值再合適不過(guò)了。

通常來(lái)講,如果你想讓視圖或控件非常醒目獨(dú)立于背景之外(比如彈出框遮罩層),你就應(yīng)該給shadowRadius設(shè)置一個(gè)稍大的值。陰影越模糊,圖層的深度看上去就會(huì)更明顯(如圖4.6).

圖4.6 大一些的陰影位移和角半徑會(huì)增加圖層的深度即視感

陰影裁剪

和圖層邊框不同,圖層的陰影繼承自內(nèi)容的外形,而不是根據(jù)邊界和角半徑來(lái)確定。為了計(jì)算出陰影的形狀,Core Animation會(huì)將寄宿圖(包括子視圖,如果有的話)考慮在內(nèi),然后通過(guò)這些來(lái)完美搭配圖層形狀從而創(chuàng)建一個(gè)陰影(見(jiàn)圖4.7)。

圖4.7 陰影是根據(jù)寄宿圖的輪廓來(lái)確定的

當(dāng)陰影和裁剪扯上關(guān)系的時(shí)候就有一個(gè)頭疼的限制:陰影通常就是在Layer的邊界之外,如果你開(kāi)啟了masksToBounds屬性,所有從圖層中突出來(lái)的內(nèi)容都會(huì)被才剪掉。如果我們?cè)谖覀冎暗倪吙蚴纠?xiàng)目中增加圖層的陰影屬性時(shí),你就會(huì)發(fā)現(xiàn)問(wèn)題所在(見(jiàn)圖4.8).

圖4.8 maskToBounds屬性裁剪掉了陰影和內(nèi)容

從技術(shù)角度來(lái)說(shuō),這個(gè)結(jié)果是可以是可以理解的,但確實(shí)又不是我們想要的效果。如果你想沿著內(nèi)容裁切,你需要用到兩個(gè)圖層:一個(gè)只畫陰影的空的外圖層,和一個(gè)用masksToBounds裁剪內(nèi)容的內(nèi)圖層。

如果我們把之前項(xiàng)目的右邊用單獨(dú)的視圖把裁剪的視圖包起來(lái),我們就可以解決這個(gè)問(wèn)題(如圖4.9).

圖4.9 右邊,用額外的陰影轉(zhuǎn)換視圖包裹被裁剪的視圖

我們只把陰影用在最外層的視圖上,內(nèi)層視圖進(jìn)行裁剪。清單4.3是代碼實(shí)現(xiàn),圖4.10是運(yùn)行結(jié)果。

清單4.3 用一個(gè)額外的視圖來(lái)解決陰影裁切的問(wèn)題

@interface?ViewController?()

@property?(nonatomic,?weak)?IBOutlet?UIView?*layerView1;

@property?(nonatomic,?weak)?IBOutlet?UIView?*layerView2;

@property?(nonatomic,?weak)?IBOutlet?UIView?*shadowView;

@end

@implementation?ViewController


-?(void)viewDidLoad

{

[superviewDidLoad];

//set?the?corner?radius?on?our?layers

self.layerView1.layer.cornerRadius?=?20.0f;

self.layerView2.layer.cornerRadius?=?20.0f;

//add?a?border?to?our?layers

self.layerView1.layer.borderWidth?=?5.0f;

self.layerView2.layer.borderWidth?=?5.0f;

//add?a?shadow?to?layerView1

self.layerView1.layer.shadowOpacity?=?0.5f;

self.layerView1.layer.shadowOffset?=?CGSizeMake(0.0f,?5.0f);

self.layerView1.layer.shadowRadius?=?5.0f;

//add?same?shadow?to?shadowView?(not?layerView2)

self.shadowView.layer.shadowOpacity?=?0.5f;

self.shadowView.layer.shadowOffset?=?CGSizeMake(0.0f,?5.0f);

self.shadowView.layer.shadowRadius?=?5.0f;

//enable?clipping?on?the?second?layer

self.layerView2.layer.masksToBounds?=?YES;

}

@end

圖4.10 右邊視圖,不受裁切陰影的陰影視圖。

shadowPath屬性

我們已經(jīng)知道圖層陰影并不總是方的,而是從圖層內(nèi)容的形狀繼承而來(lái)。這看上去不錯(cuò),但是實(shí)時(shí)計(jì)算陰影也是一個(gè)非常消耗資源的,尤其是圖層有多個(gè)子圖層,每個(gè)圖層還有一個(gè)有透明效果的寄宿圖的時(shí)候。

如果你事先知道你的陰影形狀會(huì)是什么樣子的,你可以通過(guò)指定一個(gè)shadowPath來(lái)提高性能。shadowPath是一個(gè)CGPathRef類型(一個(gè)指向CGPath的指針)。CGPath是一個(gè)Core Graphics對(duì)象,用來(lái)指定任意的一個(gè)矢量圖形。我們可以通過(guò)這個(gè)屬性單獨(dú)于圖層形狀之外指定陰影的形狀。

圖4.11 展示了同一寄宿圖的不同陰影設(shè)定。如你所見(jiàn),我們使用的圖形很簡(jiǎn)單,但是它的陰影可以是你想要的任何形狀。清單4.4是代碼實(shí)現(xiàn)。

圖4.11 用shadowPath指定任意陰影形狀

清單4.4 創(chuàng)建簡(jiǎn)單的陰影形狀

@interface?ViewController?()

@property?(nonatomic,?weak)?IBOutlet?UIView?*layerView1;

@property?(nonatomic,?weak)?IBOutlet?UIView?*layerView2;

@end

@implementation?ViewController

-?(void)viewDidLoad

{

[superviewDidLoad];

//enable?layer?shadows

self.layerView1.layer.shadowOpacity?=?0.5f;

self.layerView2.layer.shadowOpacity?=?0.5f;

//create?a?square?shadow

CGMutablePathRef?squarePath?=?CGPathCreateMutable();

CGPathAddRect(squarePath,?NULL,?self.layerView1.bounds);

self.layerView1.layer.shadowPath?=?squarePath;?CGPathRelease(squarePath);

//create?a?circular?shadow

CGMutablePathRef?circlePath?=?CGPathCreateMutable();

CGPathAddEllipseInRect(circlePath,?NULL,?self.layerView2.bounds);

self.layerView2.layer.shadowPath?=?circlePath;?CGPathRelease(circlePath);

}

@end

如果是一個(gè)舉行或是圓,用CGPath會(huì)相當(dāng)簡(jiǎn)單明了。但是如果是更加復(fù)雜一點(diǎn)的圖形,UIBezierPath類會(huì)更合適,它是一個(gè)由UIKit提供的在CGPath基礎(chǔ)上的Objective-C包裝類。

圖層蒙板

通過(guò)masksToBounds屬性,我們可以沿邊界裁剪圖形;通過(guò)cornerRadius屬性,我們還可以設(shè)定一個(gè)圓角。但是有時(shí)候你希望展現(xiàn)的內(nèi)容不是在一個(gè)矩形或圓角矩形。比如,你想展示一個(gè)有星形框架的圖片,又或者想讓一些古卷文字慢慢漸變成背景色,而不是一個(gè)突兀的邊界。

使用一個(gè)32位有alpha通道的png圖片通常是創(chuàng)建一個(gè)無(wú)矩形視圖最方便的方法,你可以給它指定一個(gè)透明蒙板來(lái)實(shí)現(xiàn)。但是這個(gè)方法不能讓你以編碼的方式動(dòng)態(tài)地生成蒙板,也不能讓子圖層或子視圖裁剪成同樣的形狀。

CALayer有一個(gè)屬性叫做mask可以解決這個(gè)問(wèn)題。這個(gè)屬性本身就是個(gè)CALayer類型,有和其他圖層一樣的繪制和布局屬性。它類似于一個(gè)子圖層,相對(duì)于父圖層(即擁有該屬性的圖層)布局,但是它卻不是一個(gè)普通的子圖層。不同于那些繪制在父圖層中的子圖層,mask圖層定義了父圖層的部分可見(jiàn)區(qū)域。

mask圖層的Color屬性是無(wú)關(guān)緊要的,真正重要的是圖層的輪廓。mask屬性就像是一個(gè)餅干切割機(jī),mask圖層實(shí)心的部分會(huì)被保留下來(lái),其他的則會(huì)被拋棄。(如圖4.12)

如果mask圖層比父圖層要小,只有在mask圖層里面的內(nèi)容才是它關(guān)心的,除此以外的一切都會(huì)被隱藏起來(lái)。

圖4.12 把圖片和蒙板圖層作用在一起的效果

我們將代碼演示一下這個(gè)過(guò)程,創(chuàng)建一個(gè)簡(jiǎn)單的項(xiàng)目,通過(guò)圖層的mask屬性來(lái)作用于圖片之上。為了簡(jiǎn)便一些,我們用Interface Builder來(lái)創(chuàng)建一個(gè)包含UIImageView的圖片圖層。這樣我們就只要代碼實(shí)現(xiàn)蒙板圖層了。清單4.5是最終的代碼,圖4.13是運(yùn)行后的結(jié)果。

清單4.5 應(yīng)用蒙板圖層

@interface?ViewController?()

@property?(nonatomic,?weak)?IBOutlet?UIImageView?*imageView;

@end

@implementation?ViewController

-?(void)viewDidLoad

{

[superviewDidLoad];

//create?mask?layer

CALayer?*maskLayer?=?[CALayer?layer];

maskLayer.frame?=?self.layerView.bounds;

UIImage?*maskImage?=?[UIImage?imageNamed:@"Cone.png"];

maskLayer.contents?=?(__bridge?id)maskImage.CGImage;

//apply?mask?to?image?layer?

self.imageView.layer.mask?=?maskLayer;

}

@end

圖4.13 使用了mask之后的UIImageView

CALayer蒙板圖層真正厲害的地方在于蒙板圖不局限于靜態(tài)圖。任何有圖層構(gòu)成的都可以作為mask屬性,這意味著你的蒙板可以通過(guò)代碼甚至是動(dòng)畫實(shí)時(shí)生成。

拉伸過(guò)濾

最后我們?cè)賮?lái)談?wù)刴inificationFilter和magnificationFilter屬性。總得來(lái)講,當(dāng)我們視圖顯示一個(gè)圖片的時(shí)候,都應(yīng)該正確地顯示這個(gè)圖片(意即:以正確的比例和正確的1:1像素顯示在屏幕上)。原因如下:

能夠顯示最好的畫質(zhì),像素既沒(méi)有被壓縮也沒(méi)有被拉伸。

能更好的使用內(nèi)存,因?yàn)檫@就是所有你要存儲(chǔ)的東西。

最好的性能表現(xiàn),CPU不需要為此額外的計(jì)算。

不過(guò)有時(shí)候,顯示一個(gè)非真實(shí)大小的圖片確實(shí)是我們需要的效果。比如說(shuō)一個(gè)頭像或是圖片的縮略圖,再比如說(shuō)一個(gè)可以被拖拽和伸縮的大圖。這些情況下,為同一圖片的不同大小存儲(chǔ)不同的圖片顯得又不切實(shí)際。

當(dāng)圖片需要顯示不同的大小的時(shí)候,有一種叫做拉伸過(guò)濾的算法就起到作用了。它作用于原圖的像素上并根據(jù)需要生成新的像素顯示在屏幕上。

事實(shí)上,重繪圖片大小也沒(méi)有一個(gè)統(tǒng)一的通用算法。這取決于需要拉伸的內(nèi)容,放大或是縮小的需求等這些因素。CALayer為此提供了三種拉伸過(guò)濾方法,他們是:

kCAFilterLinear

kCAFilterNearest

kCAFilterTrilinear

minification(縮小圖片)和magnification(放大圖片)默認(rèn)的過(guò)濾器都是kCAFilterLinear,這個(gè)過(guò)濾器采用雙線性濾波算法,它在大多數(shù)情況下都表現(xiàn)良好。雙線性濾波算法通過(guò)對(duì)多個(gè)像素取樣最終生成新的值,得到一個(gè)平滑的表現(xiàn)不錯(cuò)的拉伸。但是當(dāng)放大倍數(shù)比較大的時(shí)候圖片就模糊不清了。

kCAFilterTrilinear和kCAFilterLinear非常相似,大部分情況下二者都看不出來(lái)有什么差別。但是,較雙線性濾波算法而言,三線性濾波算法存儲(chǔ)了多個(gè)大小情況下的圖片(也叫多重貼圖),并三維取樣,同時(shí)結(jié)合大圖和小圖的存儲(chǔ)進(jìn)而得到最后的結(jié)果。

這個(gè)方法的好處在于算法能夠從一系列已經(jīng)接近于最終大小的圖片中得到想要的結(jié)果,也就是說(shuō)不要對(duì)很多像素同步取樣。這不僅提高了性能,也避免了小概率因舍入錯(cuò)誤引起的取樣失靈的問(wèn)題

圖4.14 對(duì)于大圖來(lái)說(shuō),雙線性濾波和三線性濾波表現(xiàn)得更出色

kCAFilterNearest是一種比較武斷的方法。從名字不難看出,這個(gè)算法(也叫最近過(guò)濾)就是取最近的單像素點(diǎn)而不管其他的顏色。這樣做非??欤膊粫?huì)使圖片模糊。但是,最明顯的效果就是,會(huì)使得壓縮圖片更糟,圖片放大之后也顯得塊狀或是馬賽克嚴(yán)重。

圖4.15 對(duì)于沒(méi)有斜線的小圖來(lái)說(shuō),最近過(guò)濾算法要好很多

總的來(lái)說(shuō),對(duì)于比較小的圖或者是差異特別明顯,極少斜線的大圖,最近過(guò)濾算法會(huì)保留這種差異明顯的特質(zhì)以呈現(xiàn)更好的結(jié)果。但是對(duì)于大多數(shù)的圖尤其是有很多斜線或是曲線輪廓的圖片來(lái)說(shuō),最近過(guò)濾算法會(huì)導(dǎo)致更差的結(jié)果。換句話說(shuō),線性過(guò)濾保留了形狀,最近過(guò)濾則保留了像素的差異。

讓我們來(lái)實(shí)驗(yàn)一下。我們對(duì)第三章的時(shí)鐘項(xiàng)目改動(dòng)一下,用LCD風(fēng)格的數(shù)字方式顯示。我們用簡(jiǎn)單的像素字體(一種用像素構(gòu)成字符的字體,而非矢量圖形)創(chuàng)造數(shù)字顯示方式,用圖片存儲(chǔ)起來(lái),而且用第二章介紹過(guò)的拼合技術(shù)來(lái)顯示(如圖4.16)。

圖4.16 一個(gè)簡(jiǎn)單的運(yùn)用拼合技術(shù)顯示的LCD數(shù)字風(fēng)格的像素字體

我們?cè)贗nterface Builder中放置了六個(gè)視圖,小時(shí)、分鐘、秒鐘各兩個(gè),圖4.17顯示了這六個(gè)視圖是如何在Interface Builder中放置的。如果每個(gè)都用一個(gè)淡出的outlets對(duì)象就會(huì)顯得太多了,所以我們就用了一個(gè)IBOutletCollection對(duì)象把他們和控制器聯(lián)系起來(lái),這樣我們就可以以數(shù)組的方式訪問(wèn)視圖了。清單4.6是代碼實(shí)現(xiàn)。

清單4.6 顯示一個(gè)LCD風(fēng)格的時(shí)鐘

@interface?ViewController?()

@property?(nonatomic,?strong)?IBOutletCollection(UIView)?NSArray?*digitViews;

@property?(nonatomic,?weak)?NSTimer?*timer;

@end

@implementation?ViewController

-?(void)viewDidLoad

{

[superviewDidLoad];//get?spritesheet?image

UIImage?*digits?=?[UIImage?imageNamed:@"Digits.png"];

//set?up?digit?views

for(UIView?*viewinself.digitViews)?{

//set?contents

view.layer.contents?=?(__bridge?id)digits.CGImage;

view.layer.contentsRect?=?CGRectMake(0,?0,?0.1,?1.0);

view.layer.contentsGravity?=?kCAGravityResizeAspect;

}

//start?timer

self.timer?=?[NSTimer?scheduledTimerWithTimeInterval:1.0?target:self?selector:@selector(tick)?userInfo:nil?repeats:YES];

//set?initial?clock?time

[self?tick];

}

-?(void)setDigit:(NSInteger)digit?forView:(UIView?*)view

{

//adjust?contentsRect?to?select?correct?digit

view.layer.contentsRect?=?CGRectMake(digit?*?0.1,?0,?0.1,?1.0);

}

-?(void)tick

{

//convert?time?to?hours,?minutes?and?seconds

NSCalendar?*calendar?=?[[NSCalendar?alloc]?initWithCalendarIdentifier:?NSGregorianCalendar];

NSUInteger?units?=?NSHourCalendarUnit?|?NSMinuteCalendarUnit?|?NSSecondCalendarUnit;

NSDateComponents?*components?=?[calendar?components:units?fromDate:[NSDate?date]];

//set?hours

[self?setDigit:components.hour?/?10?forView:self.digitViews[0]];

[self?setDigit:components.hour?%?10?forView:self.digitViews[1]];

//set?minutes

[self?setDigit:components.minute?/?10?forView:self.digitViews[2]];

[self?setDigit:components.minute?%?10?forView:self.digitViews[3]];

//set?seconds

[self?setDigit:components.second?/?10?forView:self.digitViews[4]];

[self?setDigit:components.second?%?10?forView:self.digitViews[5]];

}

@end

如圖4.18,這樣做的確起了效果,但是圖片看起來(lái)模糊了??雌饋?lái)默認(rèn)的kCAFilterLinear選項(xiàng)讓我們失望了。

圖4.18 一個(gè)模糊的時(shí)鐘,由默認(rèn)的kCAFilterLinear引起

為了能像圖4.19中那樣,我們需要在for循環(huán)中加入如下代碼:

view.layer.magnificationFilter?=?kCAFilterNearest;

圖4.19 設(shè)置了最近過(guò)濾之后的清晰顯示

組透明

UIView有一個(gè)叫做alpha的屬性來(lái)確定視圖的透明度。CALayer有一個(gè)等同的屬性叫做opacity,這兩個(gè)屬性都是影響子層級(jí)的。也就是說(shuō),如果你給一個(gè)圖層設(shè)置了opacity屬性,那它的子圖層都會(huì)受此影響。

iOS常見(jiàn)的做法是把一個(gè)空間的alpha值設(shè)置為0.5(50%)以使其看上去呈現(xiàn)為不可用狀態(tài)。對(duì)于獨(dú)立的視圖來(lái)說(shuō)還不錯(cuò),但是當(dāng)一個(gè)控件有子視圖的時(shí)候就有點(diǎn)奇怪了,圖4.20展示了一個(gè)內(nèi)嵌了UILabel的自定義UIButton;左邊是一個(gè)不透明的按鈕,右邊是50%透明度的相同按鈕。我們可以注意到,里面的標(biāo)簽的輪廓跟按鈕的背景很不搭調(diào)。

圖4.20 右邊的漸隱按鈕中,里面的標(biāo)簽清晰可見(jiàn)

這是由透明度的混合疊加造成的,當(dāng)你顯示一個(gè)50%透明度的圖層時(shí),圖層的每個(gè)像素都會(huì)一般顯示自己的顏色,另一半顯示圖層下面的顏色。這是正常的透明度的表現(xiàn)。但是如果圖層包含一個(gè)同樣顯示50%透明的子圖層時(shí),你所看到的視圖,50%來(lái)自子視圖,25%來(lái)了圖層本身的顏色,另外的25%則來(lái)自背景色。

在我們的示例中,按鈕和表情都是白色背景。雖然他們都死50%的可見(jiàn)度,但是合起來(lái)的可見(jiàn)度是75%,所以標(biāo)簽所在的區(qū)域看上去就沒(méi)有周圍的部分那么透明。所以看上去子視圖就高粱了,使得這個(gè)顯示效果都糟透了。

理想狀況下,當(dāng)你設(shè)置了一個(gè)圖層的透明度,你希望它包含的整個(gè)圖層樹(shù)像一個(gè)整體一樣的透明效果。你可以通過(guò)設(shè)置Info.plist文件中的UIViewGroupOpacity為YES來(lái)達(dá)到這個(gè)效果,但是這個(gè)設(shè)置會(huì)影響到這個(gè)應(yīng)用,整個(gè)app可能會(huì)受到不良影響。如果UIViewGroupOpacity并未設(shè)置,iOS 6和以前的版本會(huì)默認(rèn)為NO(也許以后的版本會(huì)有一些改變)。

另一個(gè)方法就是,你可以設(shè)置CALayer的一個(gè)叫做shouldRasterize屬性(見(jiàn)清單4.7)來(lái)實(shí)現(xiàn)組透明的效果,如果它被設(shè)置為YES,在應(yīng)用透明度之前,圖層及其子圖層都會(huì)被整合成一個(gè)整體的圖片,這樣就沒(méi)有透明度混合的問(wèn)題了(如圖4.21)。

為了啟用shouldRasterize屬性,我們?cè)O(shè)置了圖層的rasterizationScale屬性。默認(rèn)情況下,所有圖層拉伸都是1.0, 所以如果你使用了shouldRasterize屬性,你就要確保你設(shè)置了rasterizationScale屬性去匹配屏幕,以防止出現(xiàn)Retina屏幕像素化的問(wèn)題。

當(dāng)shouldRasterize和UIViewGroupOpacity一起的時(shí)候,性能問(wèn)題就出現(xiàn)了(我們?cè)诘?2章『速度』和第15章『圖層性能』將做出介紹),但是性能碰撞都本地化了(譯者注:這句話需要再翻譯)。

清單4.7 使用shouldRasterize屬性解決組透明問(wèn)題

@interface?ViewController?()

@property?(nonatomic,?weak)?IBOutlet?UIView?*containerView;

@end

@implementation?ViewController

-?(UIButton?*)customButton

{

//create?button

CGRect?frame?=?CGRectMake(0,?0,?150,?50);

UIButton?*button?=?[[UIButton?alloc]?initWithFrame:frame];

button.backgroundColor?=?[UIColor?whiteColor];

button.layer.cornerRadius?=?10;

//add?label

frame?=?CGRectMake(20,?10,?110,?30);

UILabel?*label?=?[[UILabel?alloc]?initWithFrame:frame];

label.text?=?@"Hello?World";

label.textAlignment?=?NSTextAlignmentCenter;

[button?addSubview:label];

returnbutton;

}

-?(void)viewDidLoad

{

[superviewDidLoad];

//create?opaque?button

UIButton?*button1?=?[self?customButton];

button1.center?=?CGPointMake(50,?150);

[self.containerView?addSubview:button1];

//create?translucent?button

UIButton?*button2?=?[self?customButton];

button2.center?=?CGPointMake(250,?150);

button2.alpha?=?0.5;

[self.containerView?addSubview:button2];

//enable?rasterization?for?the?translucent?button

button2.layer.shouldRasterize?=?YES;

button2.layer.rasterizationScale?=?[UIScreen?mainScreen].scale;

}

@end

圖4.21 修正后的圖

總結(jié)

這一章介紹了一些可以通過(guò)代碼應(yīng)用到圖層上的視覺(jué)效果,比如圓角,陰影和蒙板。我們也了解了拉伸過(guò)濾器和組透明。

在第五章,『變換』中,我們將會(huì)研究圖層變化和3D轉(zhuǎn)換。

--------------------------------------------------------------------------------------------------------------------------------------------------------

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,196評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事?!?“怎么了?”我有些...
    開(kāi)封第一講書人閱讀 175,688評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 62,654評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,456評(píng)論 6 406
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 54,955評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 42,195評(píng)論 0 287
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,725評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,608評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,802評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,048評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 34,422評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 35,673評(píng)論 1 281
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,424評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,762評(píng)論 2 372

推薦閱讀更多精彩內(nèi)容