Core Animation 高階技巧(四)視覺效果

weixin_34321977發表於2017-04-22

我們在第三章圖層幾何學中討論了圖層的frame,第二章寄宿圖則討論了圖層的寄宿圖。但是圖層不僅僅可以是圖片或是顏色的容器;還有一系列內建的特性使得創造美麗優雅的令人深刻的介面元素成為可能。在這一章,我們將會探索一些能夠通過使用CALayer屬性實現的視覺效果。

圓角

圓角矩形是iOS的一個標誌性審美特性。這在iOS的每一個地方都得到了體現,不論是主螢幕圖示,還是警告彈框,甚至是文字框。按照這流行程度,你可能會認為一定有不借助Photoshop就能輕易建立圓角矩形的方法。恭喜你,猜對了。

CALayer有一個叫做conrnerRadius的屬性控制著圖層角的曲率。它是一個浮點數,預設為0(為0的時候就是直角),但是你可以把它設定成任意值。預設情況下,這個曲率值隻影響背景顏色而不影響背景圖片或是子圖層。不過,如果把masksToBounds設定成YES的話,圖層裡面的所有東西都會被擷取。

我們可以通過一個簡單的專案來演示這個效果。在Interface Builder中,我們放置一些檢視,他們有一些子檢視。而且這些子檢視有一些超出了邊界(如圖4.1)。你可能無法看到他們超出了邊界,因為在編輯介面的時候,超出的部分總是被Interface Builder裁切掉了。不過,你相信我就好了

1414607-ded709ade94da6db.png
4.1.png

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

然後在程式碼中,我們設定角的半徑為20個點,並裁剪掉第一個檢視的超出部分(見清單4.1)。技術上來說,這些屬性都可以在Interface Builder的探測板中分別通過『使用者定義執行時屬性』和勾選『裁剪子檢視』(Clip Subviews)選擇框來直接設定屬性的值。不過,在這個示例中,程式碼能夠表示得更清楚。圖4.2是執行程式碼的結果

清單4.1 設定cornerRadiusmasksToBounds

@interface ViewController ()

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

@end

@implementation ViewController
- (void)viewDidLoad
{
  [super viewDidLoad];

  //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
1414607-f7b992a26756b995.png
4.2.png

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

如你所見,右邊的子檢視沿邊界被裁剪了。

單獨控制每個層的圓角曲率也不是不可能的。如果想建立有些圓角有些直角的圖層或檢視時,你可能需要一些不同的方法。比如使用一個圖層蒙板(本章稍後會講到)或者是CAShapeLayer(見第六章『專用圖層』)。

圖層邊框

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

borderWidth是以點為單位的定義邊框粗細的浮點數,預設為0.borderColor定義了邊框的顏色,預設為黑色。

borderColorCGColorRef型別,而不是UIColor,所以它不是Cocoa的內建物件。不過呢,你肯定也清楚圖層引用了borderColor,雖然屬性宣告並不能證明這一點。CGColorRef在引用/釋放時候的行為表現得與NSObject極其相似。但是Objective-C語法並不支援這一做法,所以CGColorRef屬性即便是強引用也只能通過assign關鍵字來宣告。

邊框是繪製在圖層邊界裡面的,而且在所有子內容之前,也在子圖層之前。如果我們在之前的示例中(清單4.2)加入圖層的邊框,你就能看到到底是怎麼一回事了(如圖4.3).

清單4.2 加上邊框

@implementation ViewController

- (void)viewDidLoad
{
  [super viewDidLoad];

  //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
1414607-35e69286d99c4168.png
4.3.png

圖4.3 給圖層增加一個邊框

仔細觀察會發現邊框並不會把寄宿圖或子圖層的形狀計算進來,如果圖層的子圖層超過了邊界,或者是寄宿圖在透明區域有一個透明蒙板,邊框仍然會沿著圖層的邊界繪製出來(如圖4.4).

1414607-8db27905e8d696c3.png
4.4.png

圖4.4 邊框是跟隨圖層的邊界變化的,而不是圖層裡面的內容

陰影

iOS的另一個常見特性呢,就是陰影。陰影往往可以達到圖層深度暗示的效果。也能夠用來強調正在顯示的圖層和優先順序(比如說一個在其他檢視之前的彈出框),不過有時候他們只是單純的裝飾目的。

shadowOpacity屬性一個大於預設值(也就是0)的值,陰影就可以顯示在任意圖層之下。shadowOpacity是一個必須在0.0(不可見)和1.0(完全不透明)之間的浮點數。如果設定為1.0,將會顯示一個有輕微模糊的黑色陰影稍微在圖層之上。若要改動陰影的表現,你可以使用CALayer的另外三個屬性:shadowColorshadowOffsetshadowRadius

顯而易見,shadowColor屬性控制著陰影的顏色,和borderColorbackgroundColor一樣,它的型別也是CGColorRef。陰影預設是黑色,大多數時候你需要的陰影也是黑色的(其他顏色的陰影看起來是不是有一點點奇怪。。)。

shadowOffset屬性控制著陰影的方向和距離。它是一個CGSize的值,寬度控制著陰影橫向的位移,高度控制著縱向的位移。shadowOffset的預設值是 {0, -3},意即陰影相對於Y軸有3個點的向上位移。

為什麼要預設向上的陰影呢?儘管Core Animation是從圖層套裝演變而來(可以認為是為iOS建立的私有動畫框架),但是呢,它卻是在Mac OS上面世的,前面有提到,二者的Y軸是顛倒的。這就導致了預設的3個點位移的陰影是向上的。在Mac上,shadowOffset的預設值是陰影向下的,這樣你就能理解為什麼iOS上的陰影方向是向上的了(如圖4.5).

1414607-f07c7ea305464e1a.png
4.5.png

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

蘋果更傾向於使用者介面的陰影應該是垂直向下的,所以在iOS把陰影寬度設為0,然後高度設為一個正值不失為一個做法。

shadowRadius屬性控制著陰影的模糊度,當它的值是0的時候,陰影就和檢視一樣有一個非常確定的邊界線。當值越來越大的時候,邊界線看上去就會越來越模糊和自然。蘋果自家的應用設計更偏向於自然的陰影,所以一個非零值再合適不過了。

通常來講,如果你想讓檢視或控制元件非常醒目獨立於背景之外(比如彈出框遮罩層),你就應該給shadowRadius設定一個稍大的值。陰影越模糊,圖層的深度看上去就會更明顯(如圖4.6).

1414607-6b79731d8adeedfd.png
4.6.png

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

陰影裁剪

和圖層邊框不同,圖層的陰影繼承自內容的外形,而不是根據邊界和角半徑來確定。為了計算出陰影的形狀,Core Animation會將寄宿圖(包括子檢視,如果有的話)考慮在內,然後通過這些來完美搭配圖層形狀從而建立一個陰影(見圖4.7)。

1414607-6f603d71df0d4a11.png
4.7.png

圖4.7 陰影是根據寄宿圖的輪廓來確定的

當陰影和裁剪扯上關係的時候就有一個頭疼的限制:陰影通常就是在Layer的邊界之外,如果你開啟了masksToBounds屬性,所有從圖層中突出來的內容都會被才剪掉。如果我們在我們之前的邊框示例專案中增加圖層的陰影屬性時,你就會發現問題所在(見圖4.8).

1414607-bf6a29e8f87957c3.png
4.8.png

圖4.8 maskToBounds屬性裁剪掉了陰影和內容

從技術角度來說,這個結果是可以理解的,但確實又不是我們想要的效果。如果你想沿著內容裁切,你需要用到兩個圖層:一個只畫陰影的空的外圖層,和一個用masksToBounds裁剪內容的內圖層。

如果我們把之前專案的右邊用單獨的檢視把裁剪的檢視包起來,我們就可以解決這個問題(如圖4.9).

1414607-4a542e07008e152a.png
4.9.png

圖4.9 右邊,用額外的陰影轉換檢視包裹被裁剪的檢視

我們只把陰影用在最外層的檢視上,內層檢視進行裁剪。清單4.3是程式碼實現,圖4.10是執行結果。

清單4.3 用一個額外的檢視來解決陰影裁切的問題

@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
{
  [super viewDidLoad];

  //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
1414607-5436053689763e0b.png
4.10.png

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

shadowPath屬性

我們已經知道圖層陰影並不總是方的,而是從圖層內容的形狀繼承而來。這看上去不錯,但是實時計算陰影也是一個非常消耗資源的,尤其是圖層有多個子圖層,每個圖層還有一個有透明效果的寄宿圖的時候。

如果你事先知道你的陰影形狀會是什麼樣子的,你可以通過指定一個shadowPath來提高效能。shadowPath是一個CGPathRef型別(一個指向CGPath的指標)。CGPath是一個Core Graphics物件,用來指定任意的一個向量圖形。我們可以通過這個屬性單獨於圖層形狀之外指定陰影的形狀。

圖4.11 展示了同一寄宿圖的不同陰影設定。如你所見,我們使用的圖形很簡單,但是它的陰影可以是你想要的任何形狀。清單4.4是程式碼實現。

1414607-4ccf7bea6e028be7.png
4.11.png

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

清單4.4 建立簡單的陰影形狀

@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圖層實心的部分會被保留下來,其他的則會被拋棄。(如圖4.12)

如果mask圖層比父圖層要小,只有在mask圖層裡面的內容才是它關心的,除此以外的一切都會被隱藏起來。

1414607-fdfbb8d734d6a48c.png
4.12.png

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

我們將程式碼演示一下這個過程,建立一個簡單的專案,通過圖層的mask屬性來作用於圖片之上。為了簡便一些,我們用Interface Builder來建立一個包含UIImageView的圖片圖層。這樣我們就只要程式碼實現蒙板圖層了。清單4.5是最終的程式碼,圖4.13是執行後的結果。

清單4.5 應用蒙板圖層

@interface ViewController ()

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

@implementation ViewController

- (void)viewDidLoad
{
  [super viewDidLoad];

  //create mask layer
  CALayer *maskLayer = [CALayer layer];
  maskLayer.frame = self.imageView.bounds;
  UIImage *maskImage = [UIImage imageNamed:@"Cone.png"];
  maskLayer.contents = (__bridge id)maskImage.CGImage;

  //apply mask to image layer
  self.imageView.layer.mask = maskLayer;
}
@end
1414607-0b8963bd3ff4bf45.png
4.13.png

圖4.13 使用了mask之後的UIImageView

CALayer蒙板圖層真正厲害的地方在於蒙板圖不侷限於靜態圖。任何有圖層構成的都可以作為mask屬性,這意味著你的蒙板可以通過程式碼甚至是動畫實時生成。

拉伸過濾

最後我們再來談談minificationFiltermagnificationFilter屬性。總得來講,當我們檢視顯示一個圖片的時候,都應該正確地顯示這個圖片(意即:以正確的比例和正確的1:1畫素顯示在螢幕上)。原因如下:

  • 能夠顯示最好的畫質,畫素既沒有被壓縮也沒有被拉伸。
  • 能更好的使用記憶體,因為這就是所有你要儲存的東西。
  • 最好的效能表現,CPU不需要為此額外的計算。

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

當圖片需要顯示不同的大小的時候,有一種叫做拉伸過濾的演算法就起到作用了。它作用於原圖的畫素上並根據需要生成新的畫素顯示在螢幕上。

事實上,重繪圖片大小也沒有一個統一的通用演算法。這取決於需要拉伸的內容,放大或是縮小的需求等這些因素。CALayer為此提供了三種拉伸過濾方法,他們是:

  • kCAFilterLinear
  • kCAFilterNearest
  • kCAFilterTrilinear

minification(縮小圖片)和magnification(放大圖片)預設的過濾器都是kCAFilterLinear,這個過濾器採用雙線性濾波演算法,它在大多數情況下都表現良好。雙線性濾波演算法通過對多個畫素取樣最終生成新的值,得到一個平滑的表現不錯的拉伸。但是當放大倍數比較大的時候圖片就模糊不清了。

kCAFilterTrilinearkCAFilterLinear非常相似,大部分情況下二者都看不出來有什麼差別。但是,較雙線性濾波演算法而言,三線性濾波演算法儲存了多個大小情況下的圖片(也叫多重貼圖),並三維取樣,同時結合大圖和小圖的儲存進而得到最後的結果。

這個方法的好處在於演算法能夠從一系列已經接近於最終大小的圖片中得到想要的結果,也就是說不要對很多畫素同步取樣。這不僅提高了效能,也避免了小概率因舍入錯誤引起的取樣失靈的問題

4.14.png

圖4.14 對於大圖來說,雙線性濾波和三線性濾波表現得更出色

kCAFilterNearest是一種比較武斷的方法。從名字不難看出,這個演算法(也叫最近過濾)就是取樣最近的單畫素點而不管其他的顏色。這樣做非常快,也不會使圖片模糊。但是,最明顯的效果就是,會使得壓縮圖片更糟,圖片放大之後也顯得塊狀或是馬賽克嚴重。

4.15.png

圖4.15 對於沒有斜線的小圖來說,最近過濾演算法要好很多

總的來說,對於比較小的圖或者是差異特別明顯,極少斜線的大圖,最近過濾演算法會保留這種差異明顯的特質以呈現更好的結果。但是對於大多數的圖尤其是有很多斜線或是曲線輪廓的圖片來說,最近過濾演算法會導致更差的結果。換句話說,線性過濾保留了形狀,最近過濾則保留了畫素的差異。

讓我們來實驗一下。我們對第三章的時鐘專案改動一下,用LCD風格的數字方式顯示。我們用簡單的畫素字型(一種用畫素構成字元的字型,而非向量圖形)創造數字顯示方式,用圖片儲存起來,而且用第二章介紹過的拼合技術來顯示(如圖4.16)。

4.16.png

圖4.16 一個簡單的運用拼合技術顯示的LCD數字風格的畫素字型

我們在Interface Builder中放置了六個檢視,小時、分鐘、秒鐘各兩個,圖4.17顯示了這六個檢視是如何在Interface Builder中放置的。如果每個都用一個淡出的outlets物件就會顯得太多了,所以我們就用了一個IBOutletCollection物件把他們和控制器聯絡起來,這樣我們就可以以陣列的方式訪問檢視了。清單4.6是程式碼實現。

4.17.png

圖4.17 在Interface Builder中放置的六個檢視

清單4.6 顯示一個LCD風格的時鐘

@interface ViewController ()

@property (nonatomic, strong) IBOutletCollection(UIView) NSArray *digitViews;
@property (nonatomic, weak) NSTimer *timer;

@end

@implementation ViewController

- (void)viewDidLoad
{
  [super viewDidLoad]; //get spritesheet image
  UIImage *digits = [UIImage imageNamed:@"Digits.png"];

  //set up digit views
  for (UIView *view in self.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,這樣做的確起了效果,但是圖片看起來模糊了。看起來預設的kCAFilterLinear選項讓我們失望了。

4.18.png

圖4.18 一個模糊的時鐘,由預設的kCAFilterLinear引起

為了能像圖4.19中那樣,我們需要在for迴圈中加入如下程式碼:

view.layer.magnificationFilter = kCAFilterNearest;
4.19.png

圖4.19 設定了最近過濾之後的清晰顯示

組透明

UIView有一個叫做alpha的屬性來確定檢視的透明度。CALayer有一個等同的屬性叫做opacity,這兩個屬性都是影響子層級的。也就是說,如果你給一個圖層設定了opacity屬性,那它的子圖層都會受此影響。

iOS常見的做法是把一個空間的alpha值設定為0.5(50%)以使其看上去呈現為不可用狀態。對於獨立的檢視來說還不錯,但是當一個控制元件有子檢視的時候就有點奇怪了,圖4.20展示了一個內嵌了UILabel的自定義UIButton;左邊是一個不透明的按鈕,右邊是50%透明度的相同按鈕。我們可以注意到,裡面的標籤的輪廓跟按鈕的背景很不搭調。

4.20.png

圖4.20 右邊的漸隱按鈕中,裡面的標籤清晰可見

這是由透明度的混合疊加造成的,當你顯示一個50%透明度的圖層時,圖層的每個畫素都會一半顯示自己的顏色,另一半顯示圖層下面的顏色。這是正常的透明度的表現。但是如果圖層包含一個同樣顯示50%透明的子圖層時,你所看到的檢視,50%來自子檢視,25%來了圖層本身的顏色,另外的25%則來自背景色。

在我們的示例中,按鈕和標籤都是白色背景。雖然他們都是50%的可見度,但是合起來的可見度是75%,所以標籤所在的區域看上去就沒有周圍的部分那麼透明。所以看上去子檢視就高亮了,使得這個顯示效果都糟透了。

理想狀況下,當你設定了一個圖層的透明度,你希望它包含的整個圖層樹像一個整體一樣的透明效果。你可以通過設定Info.plist檔案中的UIViewGroupOpacity為YES來達到這個效果,但是這個設定會影響到這個應用,整個app可能會受到不良影響。如果UIViewGroupOpacity並未設定,iOS 6和以前的版本會預設為NO(也許以後的版本會有一些改變)。

另一個方法就是,你可以設定CALayer的一個叫做shouldRasterize屬性(見清單4.7)來實現組透明的效果,如果它被設定為YES,在應用透明度之前,圖層及其子圖層都會被整合成一個整體的圖片,這樣就沒有透明度混合的問題了(如圖4.21)。

為了啟用shouldRasterize屬性,我們設定了圖層的rasterizationScale屬性。預設情況下,所有圖層拉伸都是1.0, 所以如果你使用了shouldRasterize屬性,你就要確保你設定了rasterizationScale屬性去匹配螢幕,以防止出現Retina螢幕畫素化的問題。

shouldRasterizeUIViewGroupOpacity一起的時候,效能問題就出現了(我們在第12章『速度』和第15章『圖層效能』將做出介紹),但是效能碰撞都本地化了(譯者注:這句話需要再翻譯)。

清單4.7 使用shouldRasterize屬性解決組透明問題

@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
1414607-562705957824c1e2.png
4.21.png

圖4.21 修正後的圖

總結

這一章介紹了一些可以通過程式碼應用到圖層上的視覺效果,比如圓角,陰影和蒙板。我們也瞭解了拉伸過濾器和組透明。

在第五章,『變換』中,我們將會研究圖層變化和3D轉換。

相關文章