《iOS Core Animation》學(xué)習(xí)筆記:寄宿圖

contents屬性

CALayer有一個屬性叫做contents,這個屬性的類型被定義為AnyObject?,意味著它可以是可空的任意類型對象。在這種情況下,可以給contents屬性賦任何值,你的App仍然能夠編譯通過。但是在實踐中,如果給contents賦的不是CGImage,那么圖層將是空白的。

contents屬性類型之所以被定義為AnyObject?,是因為在Mac OS系統(tǒng)上,這個屬性對CGImage和NSImage類型的值都起作用。如果在iOS平臺上將UIImage的值賦給它,只能得到一個空白的圖層。

基于上一個DemoCode,我們這次直接把layerView的宿主圖層的contents屬性設(shè)置成圖片

import UIKit

class ViewController: UIViewController {

@IBOutlet weak var layerView: UIView!

override func viewDidLoad() {
    super.viewDidLoad()
    
    let image = UIImage(named: "snowman")
    
    self.layerView.layer.contents = image?.CGImage
  }
}
UIView的宿主圖層中顯示一張圖片

contentGravity屬性

在使用UIImageView的時候遇到過圖片被拉伸的情況,解決方案就是把contentMode屬性設(shè)置成某個合適的值,例如:

imageView.contentMode = .ScaleAspectFit

CALayer與contentMode對應(yīng)的屬性叫做contentsGravity,但是它是一個String類型,而不是一個枚舉類型。contentsGravity可選的常用值有以下這些:

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

和contentMode一樣,contentsGravity的目的是為了決定內(nèi)容在圖層的邊界中怎樣對齊,假如使用kCAGravityResizeAspect,它的效果等同于UIViewContentModeScaleAspectFit,同時它還能在圖層中等比例拉伸以適應(yīng)圖層的邊界。

self.layerView.layer.contentsGravity = kCAGravityResizeAspect

contentScale屬性

contentScale屬性定義了寄宿圖的像素尺寸和視圖大小的比例,默認(rèn)情況下它是一個值為1.0的浮點數(shù)。

contentScale屬性其實屬于支持高分辨率屏幕機制的一部分。它用來判斷在繪制圖層的時候應(yīng)該為寄宿圖創(chuàng)建的空間大小和需要顯示的圖片的拉伸度。

如果contentScale值為1.0,將會以每一個點1個像素繪制圖片,如果設(shè)置為2.0,則會以每個點2個像素繪制圖片,就是熟知的Retina屏幕。

當(dāng)用代碼的方式來處理寄宿圖時候,一定要記住要手動的設(shè)置圖層的contentScale屬性,否則圖片在Retina設(shè)備上就顯得不正確。

self.layerView.layer.contentsScale = UIScreen.mainScreen().scale

masksToBounds屬性

UIView有一個叫clipToBounds的屬性,可以用來決定是否顯示超出邊界的內(nèi)容,CALayer對應(yīng)的屬性叫做masksToBounds,設(shè)置值為true,內(nèi)容就不會超出邊界了。

self.layerView.layer.masksToBounds = true
圖:使用masksToBounds來修建圖層內(nèi)容

contentsRect屬性

CALayer的contentsRect屬性允許在圖層邊框里顯示寄宿圖的一個子域。

和bounds、frame不同,contentsRect不是按照點來計算的,它使用的是單位坐標(biāo),單位坐標(biāo)指定在0到1之間,是一個相對值(像素和點就是絕對值)。

iOS使用了以下的坐標(biāo)系統(tǒng)

  • 點:在iOS和Mac OS中最常見的坐標(biāo)體系。點就像虛擬的像素,也被稱作邏輯像素。在標(biāo)準(zhǔn)設(shè)備上,一個點就是一個像素,但是在Retina設(shè)備上,一個點等于22個像素或33個像素。iOS用點作為屏幕的坐標(biāo)測算體系就是為了在Retina設(shè)備和普通設(shè)備上能有一致的視覺效果。
  • 像素:物理像素坐標(biāo)并不會用來屏幕布局,但是仍然與圖片有相對關(guān)系。UIImage是一個屏幕分辨率解決方案,所以指定點來度量大小。但是一些底層的圖片表示,如CGImage就會使用像素,所以我們要清楚在Retina設(shè)備和普通設(shè)備上,它們表現(xiàn)出來了不同的大小。
  • 單位:對于與圖片大小或是圖層邊界相關(guān)的顯示,單位坐標(biāo)是一個方便的度量方式,當(dāng)大小改變的時候,也不需要再次調(diào)整。單位坐標(biāo)在OpenGL這種紋理坐標(biāo)系統(tǒng)中用的很多,Core Animation中也用到了單位坐標(biāo)。

默認(rèn)的contentsRect值是{0,0,1,1},意味著整個寄宿圖默認(rèn)都是可見的。

事實上給contentsRect設(shè)置一個負(fù)數(shù)的原點或是大于{1,1}的尺寸也可以。這種情況下,最外面的像素會被拉伸以填充剩下的區(qū)域。

下面加入一些代碼,可以只顯示snowman的右上角四分之一的內(nèi)容,而layer的大小保持不變,所以顯示的內(nèi)容會被拉伸。

// 選擇右上角四分之一為內(nèi)容
self.layerView.layer.contentsRect = CGRectMake(0.5, 0, 0.5, 0.5)
//拉伸
self.layerView.layer.contentsGravity = kCAGravityResize
顯示右上角四分之一內(nèi)容,且拉伸

contentsCenter屬性

從contentsCenter屬性名字看,初學(xué)者很有可能認(rèn)為它和圖片的位置有關(guān),不過這個名字誤導(dǎo)了你。
contentsCenter其實是一個CGRect,它定義了一個固定的邊框和一個在圖層上可拉伸的區(qū)域。
默認(rèn)情況下,contentsCenter是{0,0,1,1},這意味著如果大?。ㄓ蒫ontentsGravity決定)改變了,那么寄宿圖將會均勻地拉伸。

如果contentsCenter屬性是上圖中間的藍(lán)色方框,那么當(dāng)這個圖片被拉伸后,contentsCenter屬性定義的區(qū)域會被全面拉伸(也就是從四個方向進(jìn)行放大或縮?。贿@個方框分隔后的其它方格會按照上圖所示的進(jìn)行橫向或者縱向的拉伸,或者某些方框根本不拉伸,這就是contentsCenter屬性的意義。

contentsCenter屬性和contentsRect屬性一樣,同樣是以比例作為單位。兩個屬性可以疊加,如果contentsRect屬性被設(shè)置,contentsCenter屬性就會操作contentsRect屬性所定義的范圍。

下面加入一些代碼,基于上個snowman效果,把左下角的四分之一部分進(jìn)行拉伸

 //左下角四分之一拉伸
 self.layerView.layer.contentsCenter = CGRectMake(0, 0.5, 0.5, 0.5)

也可以在Interface Builder里配置,而不需要寫代碼

Custom Drawing

為contents賦CGImage值并不是唯一設(shè)置寄宿圖的方法。我們也可以直接用Core Graphics直接繪制寄宿圖。

下面通過代碼實現(xiàn)CALayerDelegate來繪制圖層

import UIKit

class ViewController: UIViewController {

@IBOutlet weak var layerView: UIView!

override func viewDidLoad() {
    super.viewDidLoad()

    // 創(chuàng)建子layer
    let blueLayer = CALayer()
    blueLayer.frame = CGRectMake(50.0, 50.0, 100.0, 100.0)
    blueLayer.backgroundColor = UIColor.blueColor().CGColor
    
    //設(shè)置layer的delegate
    blueLayer.delegate = self
    
    //確保layer的寄宿圖使用正確的scale
    blueLayer.contentsScale = UIScreen.mainScreen().scale
    
    self.layerView.layer.addSublayer(blueLayer)
    
    //強制layer重繪
    blueLayer.display()
  }

override func drawLayer(layer: CALayer, inContext ctx: CGContext) {
    
    CGContextSetLineWidth(ctx, 10.0)
    
    CGContextSetStrokeColorWithColor(ctx, UIColor.redColor().CGColor)
    
    CGContextStrokeEllipseInRect(ctx, layer.bounds)
  }
}

注意

  • blueLayer上顯示地調(diào)用了display()。不同于UIView,當(dāng)圖層顯示在屏幕上時,CALayer不會自動重繪它的內(nèi)容,它把重繪的決定權(quán)交給了開發(fā)者。
  • 盡管這里沒有設(shè)置masksToBound屬性,繪制的那個圓仍然沿著邊界被裁減了。這是因為當(dāng)使用CALayerDelegate繪制寄宿圖的時候,并沒有對超出邊界外的內(nèi)容提供繪制支持。

最后除非你創(chuàng)建了一個單獨的圖層,你幾乎沒有機會用到CALayerDelegate協(xié)議。因為當(dāng)UIView創(chuàng)建了它的寄宿圖層時,它會自動地把圖層的delegate設(shè)置為自己,并提供了一個displayLayer的實現(xiàn)。

示例代碼地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容