iOS CALayer 基礎

歡迎你們關注個人公衆號,我會按期分享一些我在項目中遇到問題的解決辦法和一些iOS實用的技巧,現階段主要是整理出一些基礎的知識記錄下來
性能

文章也會同步更新到個人博客:
ppsheep.com學習

動畫的由來

在iOS中全部的視圖都是從UIView的基類派生而來,UIView能夠處理觸摸事件,能夠支持基於Core Graphics繪圖,也可以作一些簡單的動畫,好比旋轉、縮放或者其餘一些滑動漸變的動畫。可是實際上,這是蘋果爲咱們封裝了一層,真正實現動畫的實際上是一個叫作圖層的玩意兒(CALayer),UIView所作的一切動畫,都是蘋果從CALayer封裝而來。動畫

CALayer

CALayer類在概念上和UIView相似,也能夠像View同樣添加一些子layer(圖片,文本等等),也可以像View同樣,管理子圖層的位置大小等等,而且,CALayer有一些很是重要的屬性和方法,iOS中的動畫就是經過這些來作動畫和變換,CALayer和UIView最大的不一樣就是CALayer不處理用戶的交互。spa

UIView和CALayer的關係

每個UIView都有一個CALayer實例屬性,UIView的職責就是建立並建立這個圖層,以確保在子視圖被建立時,子圖層也可以被建立,子視圖被添加和移除的時候,子圖層也可以作相對的添加移除操做。 他們的關係是一一對應的。3d

實際上,咱們在屏幕上看到的視圖或者動畫,其實都是圖層。UIView只是蘋果爲咱們封裝的高級API指針

這個就有個歷史緣由了,主要呢 是要在Mac上也使用CALayer,可是iOS設備的觸摸和Mac的鼠標點擊又不同,在Mac上,高級API就叫作NSView了,更多了,就不在這裏講了。code

哪裏能用到CALayer

通常的,咱們在處理一些簡單的動畫時,都用不到CALayer,既然蘋果爲咱們封裝好了,幹嗎不用呢?可是若是須要再處理一些高級的動畫,那UIView可能就不能知足咱們的需求了。cdn

沒有暴露出來的CALayer功能:對象

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

使用圖層

咱們先來感覺一下圖層blog

新建一個工程

在view中添加一個view

UIView *layerView = [[UIView alloc] initWithFrame:CGRectMake(10, 10, 200, 200)];
layerView.backgroundColor = [UIColor redColor];
layerView.center = self.view.center;
[self.view addSubview:layerView];複製代碼

而後咱們想要在這個小紅方框的中間添加上一個藍色的小方框。固然,咱們確定知道這很簡單,直接加上一個子view就好了,可是這樣作的,就失去了咱們學習CALayer的意義。

個人想法是這樣,既然layer像view同樣,那咱們是否能夠在layerView的layer上加上一個藍色方框樣式的layer 咱們的作法是這樣

CALayer *blueLayer = [CALayer layer];
blueLayer.frame = CGRectMake(50, 50, 100, 100);
blueLayer.backgroundColor = [UIColor blueColor].CGColor;
[layerView.layer addSublayer:blueLayer];複製代碼

效果就出來了 咱們來看看3D圖

咱們能夠看到,這樣的效果,咱們只能看到一個圖層,一個view 並無向layerView上添加子view

固然這裏咱們只是作一個layer的介紹,並非說你以後添加視圖這樣添加,這樣確定是錯誤的,咱們以前講過,layer不能處理用戶的交互,這個很重要。

可是在什麼狀況下,咱們須要這樣來使用CALayer呢?

  • 開發同時能夠在macOS上運行的跨平臺應用
  • 使用多種CALayer,並不想建立額外的UIView去封裝他們(這個後面會講到)
  • 作一些對性能要求較高的工做,可是遇到這種狀況,咱們不少時候都直接使用OpenGL繪圖了

總的來講呢,處理視圖確定比處理圖層簡單多了

咱們這裏建立這一個例子,只是爲了來介紹,圖層的樹狀結構,和視圖的一一關聯關係。

寄宿圖

寄宿圖是什麼意思呢?其實呢,就是圖層中包含圖

contents屬性

在CALayer中有一個屬性,叫作contents,這個屬性的類型被定義爲id,看上去好像這個屬性可以接收任意類型的值,若是給contents賦予了任意一個類型的值,你的APP也可以編譯成功,可是獲得的圖層確實一個空白的圖層,事實上,這個contents在iOS下,是須要一個CGImage的值。

那爲何這個又要寫做一個id類型呢,這個又是一個歷史緣由了 ,明顯的這個是由於macOS的緣由,由於在macOS下,這個是接收NSImage類型。

在UIImage中 有一個CGImage屬性,他返回的是一個CGImageRef(指向CGImage的指針),若是你直接把這個賦給contents,那是會編譯出錯的。CGImageRef是一個Core Foundation對象,並非一個cocoa對象,可是咱們能夠經過bridged來進行轉換,咱們來向剛剛建立的layerView的圖層賦予一張圖片

UIImage *image = [UIImage imageNamed:@"plane"];
layerView.layer.contents = (__bridge id _Nullable)(image.CGImage);複製代碼

這樣咱們就避開了UIImageView,直接向UIView的圖層設置一張圖片

contentsGravity屬性

可是咱們看到中間的圖片明顯被拉伸了,咱們想要展現他原有的效果,怎麼作呢?

在咱們使用UIImageView時,咱們處理這種拉伸,通常是使用UIimageView的一個屬性

imageView.contentModel = UIViewContentModeScaleAspectFit;複製代碼

在CALayer中有一個和contentMode類似的屬性,叫作contentsGravity,不一樣的是他是一個NSString類型。

contentsGravity值的類型包含:

  • kCAGravityCenter
  • kCAGravityTop
  • kCAGravityBottom
  • kCAGravityLeft
  • kCAGravityRight
  • kCAGravityTopLeft
  • kCAGravityTopRight
  • kCAGravityBottomLeft
  • kCAGravityBottomRight
  • kCAGravityResize
  • kCAGravityResizeAspect
  • kCAGravityResizeAspectFill

這其中的類型與contentMode都有一一對應關係的,其中kCAGravityResizeAspect至關於視圖中contentMode類型的UIViewContentModeScaleAspectFit

當咱們將layerView的contentsGravity設置成kCAGravityResizeAspect

layerView.layer.contentsGravity = kCAGravityResizeAspect;複製代碼

效果就不同了

contentsScale屬性(主要針對Mac)

contentsScale定義了寄宿圖的像素尺寸和視圖大小的比例,默認狀況下是一個值爲1.0的浮點數。那麼咱們通常怎麼使用這個屬性呢?

這個屬性實際上是爲了支持高分辨率屏幕機制而出現的,他用來判斷在繪製圖層的時候,應該爲寄宿圖建立建立的空間大小和須要顯示圖片的拉伸度。

簡單來講,若是咱們將contentsScale設置爲1.0 那麼寄宿圖建立出來的圖片將會以每一個點一個像素來繪製圖片,若是設置爲2,那麼將會以每一個點2個像素來繪製圖片。這個就是咱們熟知的retina屏幕。

在咱們以前使用contentsGravity = kCAGravityResizeAspect這個屬性時,默認是將圖片等比例拉伸至適應圖層大小,可是,若是咱們將contentsGravity設置成kCAGravityCenter,咱們看一下,效果會是什麼樣?

整個圖片直接放大,將原圖層蓋住,這是由於kCAGravityCenter屬性值,默認是不會對圖片進行拉伸,因此將圖片的原始大小展現了出來,這時候,咱們的contentsScale就起到了做用

layerView.layer.contentsScale = image.scale;複製代碼

這時,咱們看到,如今使用了正確的圖片來進行繪製

注意,當咱們使用代碼來設置寄宿圖時,咱們必定要手動設置contentsScale

layerView.layer.contentsScale = [UIScreen mainScreen].scale;複製代碼

這樣,咱們的圖片在retina設備上,纔會顯示正常。

maskToBounds屬性

不知你們有沒有注意到,在上面的圖中,咱們的圖片已經超過了圖層的邊界,默認狀況下,在UIView中,也會繪製超過邊界的內容或者子視圖。

在UIView中,控制是否超出邊界的屬性是clipsToBounds,在CALayer中,控制的屬性是masksToBounds,將他設置成yes

contentsRect屬性

contentRect屬性,容許咱們在圖層中顯示寄宿圖的部分區域,這涉及到圖片的顯示和圖片是如何拉伸的,因此比contentsGravity靈活得多,這裏也會多講一下。

雖然這個屬性有帶一個rect的樣式,這樣很容易讓咱們想到bounds和frame,可是這個屬性和他們的使用方法確是不同的。他使用的是單位座標,單位座標指定在0到1之間,是一個相對值,是相對於寄宿圖的位置和大小。

默認的contentsRect是{0,0,1,1} 這樣就表示寄宿圖的所有區域。若是咱們指定一個區域,那麼寄宿圖就會被顯示一部分區域

能夠看到如今寄宿圖是所有顯示的,這時候咱們來設置一下layer的contentsRect屬性

layerView.layer.contentsRect = CGRectMake(0.5, 0.5, 0.5, 0.5);複製代碼

這時候,圖層就會這樣顯示

明顯咱們能夠看到 這是顯示的圖片的右下角區域 這樣顯示也是咱們給定的{0.5,0.5,0.5,0.5}決定的 從圖片的中點位置開始,顯示半寬半高

那麼咱們在作APP時,什麼地方常用到這個屬性呢?

咱們常常在圖片拼合的時候用到這個屬性,這個圖片拼合概念在遊戲開發中常常碰到。
說簡單點,就是將多張圖片打包成一張圖片,而後一次性載入這一賬圖片,這樣帶來的好處就是可以在內存使用上,屏幕渲染上節省不少性能。

例子:這種使用方法,咱們常見的APP中,不少美顏的相機使用了layer添加一些效果圖片到當前的顯示視圖上,好比添加一個什麼笑臉啊,相框啊之類的。

contentsCenter屬性

咋一看,咱們會覺得這個和寄宿圖的位置相關,其實不是的,這個屬性主要是用來控制圖片的拉伸,其實他是一個CGRect,他定義了一個固定的邊框和一個在圖層上能夠拉伸的區域,改變contentsCenter的值,並不會影響寄宿圖的顯示,除非這個圖層的大小改變了,咱們才能看獲得結果

contentsCenter默認的大小是{0,0,1,1},這就意味着,若是圖層的大小改變了,那麼整個寄宿圖都會被均勻拉伸,若是咱們改變contentsCenter這個屬性,定義一個拉伸的區域,那麼咱們就能看到效果了

注意
這裏我畫了一張圖,整個一個方框表示的是一個圖層,若是咱們將圖層的contentsCenter設置爲{0.25,0.25,0.25,0.25}那麼,其實這個rect造成的一個方框就是中間的I區域,至關於整個圖層的正中心,寬高各一半的位置 若是咱們如今改變了圖層的大小的話,咱們定義了這樣一個拉伸區域{0.25,0.25,0.25,0.25},表示的就是在橫向拉伸中H區域和D區域會被拉伸,在縱向拉伸中,B區域和F區域會被拉伸,而中間的I區域則是橫向縱向均會被拉伸,而其中的A、C、E、G則不會被拉伸,可能這裏須要着重理解一下

到這裏,基本上就將CALayer中咱們可能會常用到的一些重要屬性講解了一下

相關文章
相關標籤/搜索