iOS核心動畫進階技巧之CALayer(一),ios核心動畫calayer

來源:互聯網
上載者:User

iOS核心動畫進階技巧之CALayer(一),ios核心動畫calayer

  UIView和CALayer的關係
  在iOS中一個UIView對應著一個CALayer,視圖的職責就是建立並管理這個圖層,以確保當子視圖在層級關係中添加或者被移除的時候,他們關聯的圖層也同樣對應在層級關係樹當中有相同的操作.實際上這些背後關聯的圖層才是真正用來在螢幕上顯示和做動畫,UIView僅僅是對它的一個封裝,提供了一些iOS類似於處理觸摸的具體功能,以及Core Animation底層方法的進階介面。(CALayer並不能響應事件,它提供了幾個能判斷一個觸點時候再圖層的範圍之內)

  CALayer存在的意義
  之所以要提供CALayer和UIView這兩個平行層級呢,一方面這樣可以做到職責分離,可以避免很多重複的代碼,另一方面由於iOS和Mac OS的介面其實沒多大差別的,但是由於iOS的多點觸摸的使用者介面和Mac基於滑鼠鍵盤有著本質區別,所以提供一個公用的CALayer來提供介面,分別提供UIView和NSView來提供事件

  CALayer的使用
  平時我們是這樣使用UIView的:

1 let blueView = UIView(frame: CGRectMake(100, 100, 100, 100))2 blueView.backgroundColor = UIColor.blueColor()3 self.view.addSubview(blueView)

  而我們可以這樣使用CALayer,你可以直接用下面這段話直接替換掉上面的代碼,程式在外觀上不會有任何區別 

1 let blueLayer = CALayer()2 blueLayer.frame = CGRectMake(100, 100, 100, 100)3 blueLayer.backgroundColor = UIColor.blueColor().CGColor4 self.view.layer.addSublayer(blueLayer)

  CALayer內容(contents)相關屬性
contents屬性
  layer有一個contents屬性,它需要傳入一個id(AnyObject!)類型,這是由於它在iOS平台需要CGImage而Mac需要NSImage,在OC中你需要用id類型強轉一下,在Swift中你只需要直接賦一個CGImage就可以了,因為任何一個Class類型的對象都能賦值給AnyObject,如果你傳入其它對象,程式不會報錯,只是圖片不會顯示出來,UIImageView之所以能顯示圖片內部也是使用了這個contents屬性的緣故

contentGravity屬性
  contentGravity屬性對應於view的contentMode屬性,可以控制layer怎樣對應和展開,雖然它的值是字串,但是swift幫它提供了常量字串把每個字串對應了起來

contentsScale屬性
  contentsScale屬性和UIView中的contentScaleFactor是對應的,它決定了一個圖片和視圖的比例,即螢幕一個點顯示幾個像素,這是iOS裝置做螢幕適配的原理,一個UIImage是包含scale,direction等資訊,而轉化成CGImage會丟失這些資訊,自己可以通過contentsScale屬性把image.scale設定給它.

maskToBounds屬性
  maskToBounds屬性對應於CALayer的masksToBounds屬性,如果設定為true,外部就裁剪了

contentsRect屬性
  contentsRect屬性允許我們在圖層裡顯示圖片的一部分,這個圖片的裁剪地區就是這個屬性,它是一個CGRect,利用它你可以做圖片拼合(即把一套圖片集合在成一張圖片,再對這個圖片裁剪處理了再使用),這樣在記憶體使用量/載入時間/渲染效能等方面都有優勢,它的值是按比例的,最大是1.

contentsCenter屬性
  contentsCenter屬性對應著UIImage的resizableImageWithCapInsets,它的值是一個CGRect,它代表的放大的地區,它的效果是黨contentScale放大的時候,只放大contentsCenter地區,其它地區壓縮

  iOS繪圖
  CGImage並不是唯一可以賦值給contents屬性的,也可以使用Core Graphics繪製寄宿圖給它,如果你實現了drawRect方法,然後如果你調用setNeedsDisplay或者外觀屬性被改變時,它就會自動調用drawRect自動重繪,雖然drawRet是一個UIView方法,但是其實都是底層都是CALayer重繪儲存了圖片,如果你不需要自訂繪製就不要寫一個空的drawRect方法,它很消耗cpu和記憶體資源
CALayer有一個可選的delegate屬性,如果設定了delegate,並主動調用了layer的displey方法(注意和drawRect不同這個重繪時機是開發人員自己控制的,也可以調用setNeedsDisplay方法給系統自己找時機調用),它會調用displayLayer(layer:CALayer!)方法,在這裡是設定contents屬性的最後機會了,如果你沒有實現這個方法,它會嘗試去調用下面這個方法:drawLayer(layer:CALayer!,inContext ctx:CGContext!),如果你實現了displayLayer方法,下面這個方法就不會調用了,drawLayer這個方法裡你可以做繪圖

1 override func drawLayer(layer: CALayer!, inContext ctx: CGContext!) {2     CGContextSetLineWidth(ctx, 10.0)3     CGContextSetStrokeColorWithColor(ctx, UIColor.redColor().CGColor);4     CGContextStrokeEllipseInRect(ctx, layer.bounds)d0605579435 }6 override func displayLayer(layer: CALayer!) {7     layer.contents = UIImage(named: "11.png")?.CGImage8 }

     由於一個UIView它會把它對應的CALayer的delegate設定為它自己,所以你不能再設定其它的layer的delegate為它,在UIView中都用drawRect方法,而delegate的使用只能單獨的使用一個層.

   圖層幾何學

UIView的frame/bounds/center對應CALayer的frame/bounds/position,center和position是對應父圖層的anchorPoint的所在位置,UIView的frame/bounds/center僅僅是存取方法,操縱UIView的這幾個屬性其實是改變CALayer對應的這幾個屬性.而CALayer的frame屬性又是個計算屬性,它是根據bounds/position/transform三個屬性計算出來的,而你改變frame的值也可能影響到其中的值,如果你做旋轉和縮放後frame和bounds可能不再一致了,bounds就是寬高,而frame還要計算旋轉後x和y軸占的空間,如

  CALayer通過anchorPoint(錨點)和center(position)對齊來控制UIView的位置,錨點是相對UIView的一個位置,而center就是一個點,由於anchorPoint屬性對UIView是屏蔽的,而anchorPoint預設值又是{0.5,0.5},所以這個屬性才叫center.而UIView和CALayertransform旋轉也是圍繞這anchorPoint旋轉的,這時候如果是一個圓周運動(比如說時鐘旋轉)就需要設定錨點的值,讓它正常旋轉.如

            

  在CALayer和UIView都有一套可以把它相對於當前父圖層的位置轉換成相對其它圖層(或view)的位置,Mac OS和iOS的座標系統是相反的,iOS左上Mac OC左下,你可以用layer的geometryFlipped屬性來適配,它可以翻轉座標系.layer的zPosition屬性是設定它垂直座標軸的位置的,預設都是0,所以你只要設定為1,就會顯示在其它層的上面

  可以通過相對座標轉換判斷點擊的點是否在一個layer或view上,代碼:

 1 var point = (touches as NSSet).anyObject()?.locationInView(self.view) 2 point = blueLayer.convertPoint(point!, fromLayer: self.view.layer) 3  4 if blueLayer.containsPoint(point!) { 5     println("touch in blue") 6      7     let yellowPoint = yellowLayer.convertPoint(point!, fromLayer: blueLayer) 8     if yellowLayer.containsPoint(yellowPoint) { 9         println("touch in yellow")10     }11     12     let redPoint = redLayer.convertPoint(point!, fromLayer: blueLayer)13     if redLayer.containsPoint(redPoint) {14         println("touch in red")15     }16 }

 

  hitTest可以擷取你接觸的那個圖層:

let point = (touches as NSSet).anyObject()?.locationInView(self.view)let layer = self.view.layer.hitTest(point!)if layer == blueLayer {    println("touch in blue")}if layer == yellowLayer {    println("touch in yellow")}if layer == redLayer {    println("touch in red")}

  UIView可以通過autoresizingMask和constraints等屬性做到自適應旋轉螢幕,CaLayer也有對應的layoutManager屬性和CAConstraintLayoutManager類,但是只能在Mac OS上使用,iOS上還不支援,如果你想使用這個特性你就不能單獨使用layer,但是如果你想調整layer的大小還是可以通過設定layer的delegate,然後實現代理方法layoutSublayersOfLayer直接修改大小顏色之類,它也需要調用setNeedsLayout方法,它和UIView對應的layoutSubviews是一樣的.

  視覺效果
圓角
  layer的cornerRadius屬性可以設定圓角曲率,如果曲率大小為邊長的一半,圓角會內切於這條邊,如果是個正方形最後的結果就是個圓形,如果是裁剪子視圖也是直接按圓內裁剪

邊框
  layer的borderColor設定邊框顏色,它是CGColorRef,borderWidth設定邊框寬度,值得注意的是邊框寬度佔用的是layer的frame的寬度,它並不會在layer外面加一層邊框而是在內部產生.而layer會被子layer覆蓋但邊框不會被覆蓋,並且邊框只是顯示用的,它不會干擾觸摸和事件,有沒有邊框都是一樣的.

陰影
  陰影至少需要shadowColor/shadowOffset/shadowOpacity三個屬性才能起作用,shadowOffset的值是CGSize類型,兩個正值是朝右下角.還有一個屬性shadowRadius,它控制陰影的邊界模糊程度,預設值是3,值為0則不模糊,值越大越模糊,模糊的結果是CGSize放心顏色重,其它幾個方向也有對應的陰影,會有一個層次感.
因為陰影是在layer外部的,所以如果要裁剪超出layer的子視圖則需要使用maskToBounds屬性,而這時陰影也會被裁剪掉,所以當maskToBounds和陰影共存時需要特殊處理下:

 1 blueLayer = CALayer() 2 blueLayer.frame = CGRectMake(50, 100, 300, 300) 3 blueLayer.backgroundColor = UIColor.blueColor().CGColor 4 blueLayer.cornerRadius = blueLayer.bounds.size.width / 2.0 5 blueLayer.borderColor = UIColor.purpleColor().CGColor 6 blueLayer.borderWidth = 10.0 7  8 blueLayer.masksToBounds = true 9 10 let shadowLayer = CALayer()11 shadowLayer.frame = blueLayer.frame12 shadowLayer.cornerRadius = blueLayer.cornerRadius13 shadowLayer.shadowColor = UIColor.redColor().CGColor14 shadowLayer.shadowOffset = CGSizeMake(23 , 23.0)15 shadowLayer.shadowOpacity = 116 shadowLayer.shadowRadius = 6017 shadowLayer.backgroundColor = UIColor.redColor().CGColor18 self.view.layer.addSublayer(shadowLayer)19 20 self.view.layer.addSublayer(blueLayer)

  需要注意的是陰影層需要有背景色,不然它的陰影顯示不出來

  shadowPath屬性可以設定陰影的形狀,注意swift中的CGMutablePath並不會增加引用計數,你不需要relase它也沒有方法可以提供給你release

1  let  circlePath = CGPathCreateMutable()2  CGPathAddEllipseInRect(circlePath, nil, self.blueLayer.bounds)3  shadowLayer.shadowPath = circlePath

  當然你也可以使用UIBezierPath來設定更複雜的形狀給shadowPath

1 let path1 = UIBezierPath(roundedRect: CGRectMake(-75, -75, 200, 200), cornerRadius: 30).CGPath2 let path2 = UIBezierPath(arcCenter: CGPointMake(25, 40), radius: 100, startAngle: 0, endAngle: 3.14159265, clockwise: true).CGPath3 shadowLayer.shadowPath = path14 shadowLayer.shadowPath = path2

  圖片蒙板

  layer的mask屬性可以設定一個layer給他,這個layer的contents應該是一個32位有alpha通道的png圖片,你可以設定一個不規則的圖片其它部分為透明,這樣就對layer設定了一個蒙板,蒙板的顏色不重要,輪廓比較重要,最後被設定了蒙板的layer它只會顯示mask蒙板形狀的內容.

1 let maskLayer = CALayer()2 maskLayer.frame = CGRectMake(0, 0, 100, 100)3 maskLayer.contents = UIImage(named: "111.png")?.CGImage4 blueLayer.mask = maskLayer

 

  展開

  如果設定的圖片不需要展開有很多好處,即不需要展開圖片,又能合理的使用記憶體和cpu,但是很多時候一張圖片要多個位置使用,所以需要展開,iOS跟我們提供了3中展開方式kCAFilterLinear/kCAFilterNearest/kCAFilterTrilinear,設定展開方式只需要跟layer的這兩個屬性賦對應的展開方式就可以:minification(縮小圖片)和magnification(放大圖片),這個屬性預設是kCAFilterLinear,它和kCAFilterTrilinear類似,他們都是線性,意思就是它會取兩個值的過度色,讓對應點能順滑的過渡,如果圖片有漸層色和斜線比較多就需要用這個預設的,如果圖片主要是單色而且主要是垂直方向的顏色,那麼就需要kCAFilterNearest,它是暴力的直接取周圍的顏色,那樣又不會失真,也不會太消耗cpu

  組透明

  iOS8預設就是組透明,在應用透明度之前,它會把子圖層和它整合成一個整體的圖片,那樣就沒有透明度混合的問題了,目前沒有測試出透明度出問題的情況,如果有可以設定layer的shouldRasterize的值為YES

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.