在這一章,我們將會探索 一些能夠通過使用CALayer
屬性實現的視覺效果。
圓角
圓角矩形是iOS
的一個標志性審美特性。這在iOS
的每一個地方都得到了體現, 不論是主屏幕圖標,還是警告彈框,甚至是文本框。按照這流行程度,你可能會認為一定有不借助Photoshop
就能輕易創建圓角舉行的方法。恭喜你,猜對了。
CALayer
有一個叫做 conrnerRadius
的屬性控制著圖層角的曲率。它是一個浮點數,默認為0
(為0
的時候就是直角),但是你可以把它設置成任意值。默認情況 下,這個曲率值只影響背景顏色而不影響背景圖片或是子圖層。不過,如果把masksToBounds
設置成YES
的話,圖層里面的所有東西都會被截取。
圖層邊框
CALayer
另外兩個非常有用屬性就是 borderWidth
和 borderColor
。二者共同定義了圖層邊的繪制樣式。這條線(也被稱作stroke
)沿著圖層的 bounds
繪制,同時也包含圖層的角。
borderWidth
是以點為單位的定義邊框粗細的浮點數,默認為 0. borderColor
定義了邊框的顏色,默認為黑色。
borderColor
是CGColorRef
類型,而不是UIColor
,所以它不是Cocoa
的 內置對象。不過呢,你肯定也清楚圖層引用了borderColor
,雖然屬性聲明并不能證明這一點。CGColorRef
在引用/釋放時候的行為表現得與NSObject
極其相似。但是Objective-C
語法并不支持這一做法,所以 CGColorRef
屬性即便是強引用也只能通過assign
關鍵字來聲明。
陰影
iOS
的另一個常見特性呢,就是陰影。陰影往往可以達到圖層深度暗示的效果。 也能夠用來強調正在顯示的圖層和優先級(比如說一個在其他視圖之前的彈出框),不過有時候他們只是單純的裝飾目的。
給shadowOpacity
屬性一個大于默認值(也就是0)的值,陰影就可以顯示在任意圖層之下。 shadowOpacity
是一個必須在0.0(不可見)和1.0(完全不透 明)之間的浮點數。如果設置為1.0,將會顯示一個有輕微模糊的黑色陰影稍微在圖層之上。若要改動陰影的表現,你可以使用CALayer
的另外三個屬
性: shadowColor
,shadowOffset
和 shadowRadius
。
顯而易見, shadowColor 屬性控制著陰影的顏色,和
borderColor一樣,它的類型也是
CGColorRef` 。陰影默認是黑色,大多數時候你需要的陰影也是黑色的(其他顏色的陰影看起來是不是
有一點點奇怪。。。)
shadowOffset
屬性控制著陰影的方向和距離。它是一個 CGSize
的值,寬度控制這陰影橫向的位移,高度控制著縱向的位移。shadowOffset
的默認值是 {0, -3},意即陰影相對于Y軸有3個點的向上位移。
shadowRadius
屬性控制著陰影的模糊度,當它的值是0
的時候,陰影就和視圖 一樣有一個非常確定的邊界線。當值越來越大的時候,邊界線看上去就會越來越模 糊和自然。蘋果自家的應用設計更偏向于自然的陰影,所以一個非零值再合適不過了。
通常來講,如果你想讓視圖或控件非常醒目獨立于背景之外(比如彈出框遮罩層),你就應該給 shadowRadius
設置一個稍大的值。陰影越模糊,圖層的深度 看上去就會更明顯.
陰影裁剪
和圖層邊框不同,圖層的陰影繼承自內容的外形,而不是根據邊界和角半徑來確定。為了計算出陰影的形狀,Core Animation
會將寄宿圖(包括子視圖,如果有的話)考慮在內,然后通過這些來完美搭配圖層形狀從而創建一個陰影。
當陰影和裁剪扯上關系的時候就有一個頭疼的限制:陰影通常就是在 Layer
的邊界之外,如果你開啟了 masksToBounds
屬性,所有從圖層中突出來的 內容都會被才剪掉。如果在我們之前的邊框示例項目中增加圖層的陰影屬性時,你就會發現問題所在。
從技術角度來說,這個結果是可以是可以理解的,但確實又不是我們想要的效果。如果你想沿著內容裁切,你需要用到兩個圖層:一個只畫陰影的空的外圖層,和一個用masksToBounds
裁剪內容的內圖層。我們只把陰影用在最外層的視圖上,內層視圖進行裁剪。
shadowPath屬性
我們已經知道圖層陰影并不總是方的,而是從圖層內容的形狀繼承而來。這看上
去不錯,但是實時計算陰影也是一個非常消耗資源的,尤其是圖層有多個子圖層,
每個圖層還有一個有透明效果的寄宿圖的時候。
如果你事先知道你的陰影形狀會是什么樣子的,你可以通過指定一個shadowPath
來提高性能。shadowPath
是一個 CGPathRef
類型(一個指向CGPath
的指針)。CGPath
是一個Core Graphics
對象,用來指定任意的一個 矢量圖形。我們可以通過這個屬性單獨于圖層形狀之外指定陰影的形狀。
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *layerView1;
@property (nonatomic, weak) IBOutlet UIView *layerView2;
@end
@implementation ViewController
- (void)viewDidLoad{
[super viewDidLoad];
//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;
如果是一個矩形或者是圓,用 CGPath
會相當簡單明了。但是如果是更加復雜一 點的圖形, UIBezierPath
類會更合適,它是一個由UIKit
提供的在CGPath
基礎上 的Objective-C
包裝類。
圖層蒙板
通過masksToBounds
屬性,我們可以沿邊界裁剪圖形;通過 cornerRadius
屬性,我們還可以設定一個圓角。但是有時候你希望展現的內容不是在一個矩形或圓角矩形。比如,你想展示一個有星形框架的圖片,又或者想讓一些古卷文字慢慢漸變成背景色,而不是一個突兀的邊界。
使用一個32
位有alpha
通道的png
圖片通常是創建一個無矩形視圖最方便的方法, 你可以給它指定一個透明蒙板來實現。但是這個方法不能讓你以編碼的方式動態地 生成蒙板,也不能讓子圖層或子視圖裁剪成同樣的形狀。
CALayer
有一個屬性叫做 mask
可以解決這個問題。這個屬性本身就是個 CALayer
類型,有和其他圖層一樣的繪制和布局屬性。它類似于一個子圖層,相對于父圖層(即擁有該屬性的圖層)布局,但是它卻不是一個普通的子圖層。不同于那些繪制在父圖層中的子圖層,mask
圖層定義了父圖層的部分可見區域。
mask
圖層的 Color
屬性是無關緊要的,真正重要的是圖層的輪廓。 mask 屬 性就像是一個餅干切割機, mask 圖層實心的部分會被保留下來,其他的則會被拋 棄。
如果 mask
圖層比父圖層要小,只有在 mask
圖層里面的內容才是它關心的, 除此以外的一切都會被隱藏起來。
- (void)viewDidLoad
{
[super viewDidLoad];
//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;
}
CALayer
蒙板圖層真正厲害的地方在于蒙板圖不局限于靜態圖。任何有圖層構成
的都可以作為 mask
屬性,這意味著你的蒙板可以通過代碼甚至是動畫實時生成。
拉伸過濾
最后我們再來談談 minificationFilter
和 magnificationFilter
屬性。總得來講,當我們視圖顯示一個圖片的時候,都應該正確地顯示這個圖片(意即:以正確的比例和正確的1:1像素顯示在屏幕上)。原因如下:
- 能夠顯示最好的畫質,像素既沒有被壓縮也沒有被拉伸。
- 能更好的使用內存,因為這就是所有你要存儲的東西。
- 最好的性能表現,CPU不需要為此額外的計算。
不過有時候,顯示一個非真實大小的圖片確實是我們需要的效果。比如說一個頭像或是圖片的縮略圖,再比如說一個可以被拖拽和伸縮的大圖。這些情況下,為同一圖片的不同大小存儲不同的圖片顯得又不切實際。
當圖片需要顯示不同的大小的時候,有一種叫做拉伸過濾的算法就起到作用了。它作用于原圖的像素上并根據需要生成新的像素顯示在屏幕上。
事實上,重繪圖片大小也沒有一個統一的通用算法。這取決于需要拉伸的內容, 放大或是縮小的需求等這些因素。 CALayer
為此提供了三種拉伸過濾方法,他們 是:
- kCAFilterLinear (雙線性濾波算法)
- kCAFilterNearest (最近過濾)
- kCAFilterTrilinear(三線性濾波算法)
minification
(縮小圖片)和magnification
(放大圖片)默認的過濾器都是 kCAFilterLinear
,這個過濾器采用雙線性濾波算法,它在大多數情況下都表現良好。雙線性濾波算法通過對多個像素取樣最終生成新的值,得到一個平滑的表現不錯的拉伸。但是當放大倍數比較大的時候圖片就模糊不清了。
kCAFilterTrilinear
和 kCAFilterLinear
非常相似,大部分情況下二者都看不出來有什么差別。但是,較雙線性濾波算法而言,三線性濾波算法存儲了多個 大小情況下的圖片(也叫多重貼圖),并三維取樣,同時結合大圖和小圖的存儲進而得到最后的結果。
這個方法的好處在于算法能夠從一系列已經接近于最終大小的圖片中得到想要的結果,也就是說不要對很多像素同步取樣。這不僅提高了性能,也避免了小概率因舍入錯誤引起的取樣失靈的問題
kCAFilterNearest
是一種比較武斷的方法。從名字不難看出,這個算法(也 叫最近過濾)就是取樣最近的單像素點而不管其他的顏色。這樣做非常快,也不會 使圖片模糊。但是,最明顯的效果就是,會使得壓縮圖片更糟,圖片放大之后也顯 得塊狀或是馬賽克嚴重。
總的來說,對于比較小的圖或者是差異特別明顯,極少斜線的大圖,最近過濾算
法會保留這種差異明顯的特質以呈現更好的結果。但是對于大多數的圖尤其是有很
多斜線或是曲線輪廓的圖片來說,最近過濾算法會導致更差的結果。換句話說,線
性過濾保留了形狀,最近過濾則保留了像素的差異。
組透明
UIView
有一個叫做alpha
的屬性來確定視圖的透明度。CALayer
有一個等同的屬性叫做opacity
,這兩個屬性都是影響子層級的。也就是說,如果你給一個圖 層設置了opacity
屬性,那它的子圖層都會受此影響。
iOS常見的做法是把一個控件的alpha
值設置為0.5
(50%
)以使其看上去呈現為不可用狀態。對于獨立的視圖來說還不錯,但是當一個控件有子視圖的時候就有點奇怪了,圖展示了一個內嵌了UILabel的自定義UIButton;左邊是一個不透明的 按鈕,右邊是50%透明度的相同按鈕。我們可以注意到,里面的標簽的輪廓跟按鈕 的背景很不搭調。
這是由透明度的混合疊加造成的,當你顯示一個50%
透明度的圖層時,圖層的每 個像素都會一半顯示自己的顏色,另一半顯示圖層下面的顏色。這是正常的透明度的表現。但是如果圖層包含一個同樣顯示50%
透明的子圖層時,你所看到的視圖, 50%
來自子視圖,25%
來了圖層本身的顏色,另外的25%
則來自背景色。
在我們的示例中,按鈕和表情都是白色背景。雖然他們都是50%
的可見度,但是 合起來的可見度是75%
,所以標簽所在的區域看上去就沒有周圍的部分那么透明。 所以看上去子視圖就高亮了,使得這個顯示效果都糟透了。
理想狀況下,當你設置了一個圖層的透明度,你希望它包含的整個圖層樹像一個 整體一樣的透明效果。你可以通過設置Info.plist
文件中的 UIViewGroupOpacity
為YES
來達到這個效果,但是這個設置會影響到這個應用,整個app
可能會受到不良 影響。如果 UIViewGroupOpacity
并未設置,iOS 6
和以前的版本會默認為NO
(也許以后的版本會有一些改變)。
另一個方法就是,你可以設置CALayer
的一個叫做shouldRasterize
屬性來實現組透明的效果,如果它被設置為YES
,在應用透明度之前,圖層及其子圖層都會被整合成一個整體的圖片,這樣就沒有透明度混合的問題了
為了啟用shouldRasterize
屬性,我們設置了圖層的resterizationScale
屬性。默認情況下,所有圖層拉伸都是1.0
, 所以如果你 使用了shouldRasterize
屬性,你就要確保你設置了rasterizationScale
屬 性去匹配屏幕,以防止出現Retina
屏幕像素化的問題。
@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];
return button;
}
- (void)viewDidLoad
{
[super viewDidLoad];
//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
總結
這一章介紹了一些可以通過代碼應用到圖層上的視覺效果,比如圓角,陰影和蒙板。我們也了解了拉伸過濾器和組透明。