天天看點

iOS UIView的CALayer探索 part1

CALayer

CALayer是UIView内部實作的細節,當然蘋果為開發者提供了進階API,使開發者調用簡單,間接地使動畫變得很簡單,但這種簡單會不可避免地帶來一些靈活上的缺陷,如果開發者略微想在底層作一些小改變,或者想實作一些特定的動畫,但蘋果并沒有對應的方法,這是就需要使用Core Animation。

其實圖層并不能像視圖那些處理觸摸事件,但能實作視圖不能實作的功能:

  • 陰影、圓角、帶顔色的邊框
  • 3D變換
  • 非矩形範圍
  • 透明遮罩
  • 多級非線性動畫

CALayer contents

CALayer 的contents屬性,類型:id,但它在iOS上實際是隻能接受CGImage,如果直接将UIImage值賦給它,隻能得到一個空白的圖層。這的奇怪的表現是有Mac OS的曆史原因造成。它之是以被定義為id類型,是因為在Mac OS系統上,這個屬性對CGImage和NSImage類型的值都起作用。同時,在UIImage上有個屬性CGImage,它傳回的類型實際是CGImageRef,這個CGImageRef不是Cocoa對象,而是Core Foundation類型。但我們可以通過bridge關鍵字轉換:

CALayer contentGravity

在平時的開發中,我們在加載圖檔的時候,發現會有一些圖檔拉伸的現象,我們通常會通過改變contentMode的枚舉,讓圖檔能正常顯示,代碼如下:

但有時代碼趕不上變化,有時我們會發現一些視圖超出我們規定的邊界,預設情況下,UIView仍然會繪制超過邊界的内容,我們這時會調用ClipsToBounds屬性來控制是否要顯示超出邊界的内容。

在CALayer中,也有與UIView對應的屬性:contentGravity,contentGravity是一個NSString類型,其可選的常量值如下:

/** Layer `contentsGravity' values. **/

CA_EXTERN NSString * const kCAGravityCenter
    CA_AVAILABLE_STARTING (, , , );
CA_EXTERN NSString * const kCAGravityTop
    CA_AVAILABLE_STARTING (, , , );
CA_EXTERN NSString * const kCAGravityBottom
    CA_AVAILABLE_STARTING (, , , );
CA_EXTERN NSString * const kCAGravityLeft
    CA_AVAILABLE_STARTING (, , , );
CA_EXTERN NSString * const kCAGravityRight
    CA_AVAILABLE_STARTING (, , , );
CA_EXTERN NSString * const kCAGravityTopLeft
    CA_AVAILABLE_STARTING (, , , );
CA_EXTERN NSString * const kCAGravityTopRight
    CA_AVAILABLE_STARTING (, , , );
CA_EXTERN NSString * const kCAGravityBottomLeft
    CA_AVAILABLE_STARTING (, , , );
CA_EXTERN NSString * const kCAGravityBottomRight
    CA_AVAILABLE_STARTING (, , , );
CA_EXTERN NSString * const kCAGravityResize
    CA_AVAILABLE_STARTING (, , , );
CA_EXTERN NSString * const kCAGravityResizeAspect
    CA_AVAILABLE_STARTING (, , , );
CA_EXTERN NSString * const kCAGravityResizeAspectFill
    CA_AVAILABLE_STARTING (, , , );
           

同時我們在使用CALayer的時候,同樣也會遇到繪制的内容超出了我們想要的邊界,這時我們要是查閱蘋果的API文檔,我們會發現,CALayer的屬性maskToBounds可以控制是否需要裁剪掉超過邊界的部分。

CALayer contentsRect

在上面我們聊到CALayer的maskToBounds屬性可以剪掉超過邊界的部分,但我們繼續探索一下,會發現CALayer有另一個屬性contentsRect也能滿足我們的需求.contentsRect允許開發者在圖層邊框裡顯示contentView的一個子域,同時也涉及視圖如何顯示和拉伸,可以說比contentsGravity更加靈活。

但contentsRect與bounds,frame不同,它不是按點計算的,而是使用機關坐标,機關坐标指定在0-1之間,是一個相對值(像素和點就是絕對值),iOS的坐标系統:

  • 點 — 在iOS和Mac OS中最常見的坐标體系。點就像是虛拟的像素,也被稱作邏輯像素。在标準裝置上,一個點就是一個像素,但是在Retina裝置上,一個點等于2*2個像素。iOS用點作為螢幕的坐标測算體系就是為了在Retina裝置和普通裝置上能有一緻的視覺效果。
  • 像素 — 實體像素坐标并不會用來螢幕布局,與圖檔有相對關系。UIImage是一個螢幕分辨率解決方案,是以指定點來度量大小。但是一些底層的圖檔表示如CGImage就會用到像素,同時開發者要它們在Retina裝置和普通裝置上表現不同的大小。
  • 機關 — 對于與圖檔大小或者圖層邊界相關的顯示,機關坐标是一個友善的度量方式,當大小改變的時候,也不需要再次調整。機關坐标在OpenGL這種紋理坐标系統中用得很多,Core Animation中也用到了機關的坐标。

contentsRect預設值是{0, 0, 1, 1},contents的内容都顯示出來,但我們要是修改小一點,那内容就會被裁剪。要是我們在contentsRect中設定一個負數的原點或是大于{1,1}的尺寸時,就會發現外面的像素會被拉伸以填充剩下的區域。

在遊戲引擎Cocos2D中使用了一個拼合技術顯示圖檔,拼合技術意思是圖檔拼合後可以打包整合到一張大圖檔上一次性載入。相比多次載入,拼合技術有諸多好處:更少的記憶體使用,更少的加載時間,優秀的圖檔渲染性能等等。其實我們也可以在應用中使用同樣的拼合技術,用我們在上文所聊到的CALayer的contentRect就可以實作了。

CALayer contentsCenter

contentsCenter其實是一個CGRect,這個屬性定義了一個固定的邊框和在圖層上拉伸的區域。我們改變 contentCenter的值不會影響到圖層内容的顯示,除非我們改變這個圖層的大小。在預設的情況下,contentCenter的預設值是{0, 0, 1, 1},要是我們通過改變contentGravity改變大小,那麼圖層的内容就會均勻地拉伸開。它的工作效果和UIImage裡的-resizableImageWithCapInsets:相似,但contentCenter可以應用到任何圖層的内容上。

CALayer anchorPoint

在UIView中的center和CALayer的position都指定了anchorPoint相對于父圖層的位置。圖層的anchorPoint是由position來控制frame的。換句話來說,anchorPoint位于圖層的中點,圖層将會以這個點為中心放置。anchorPoint屬性并沒有再UIView中聲明,是以這也是視圖的position被叫做center的原因。但是圖層anchorPoint可以移動的。比如我們可以吧它置于圖層frame的左上角,那麼圖層的内容将會向右下角的position方向移動。