iOS開發經驗(8)-繪圖

目錄:
  1. 主要繪圖框架介紹
  2. CALayer
  3. 繪圖
  4. 貝塞爾曲線-UIBezierPath
  5. CALayer子類
  6. 補充:iOS角度與弧度轉換
  7. 補充:附圖
1. 主要繪圖框架介紹

關系.png

基本概念

CoreGraphics,OpenGL,QuartzCore,QuartZ 2D,CoreAnimation幾個框架區別不了解,在此根據關系圖譜梳理一下:

** iOS支持兩套圖形API族:Core Graphics/QuartZ 2DOpenGL ES。**
Core Graphics Framework是一套基于CAPI框架,使用了Quartz作為繪圖引擎。Core Graphics是一個繪圖專用的API族,它經常被稱為QuartZQuartZ 2DCore GraphicsiOS上所有繪圖功能的基石,包括UIKit。該框架可以用于基于路徑的繪圖、變換、顏色管理、離屏渲染,模板、漸變、遮蔽、圖像數據管理、圖像的創建、遮罩以及PDF文檔的創建、顯示和分析。它是iOS的核心圖形庫,類名以CG開頭的都屬于CoreGraphics框架,它提供的都是C語言的函數接口,是可以在iOSMac OS X通用的。
QuartZ 2D是蘋果公司開發的一套API,它是Core Graphics Framework的一部分。
OpenGL ES是跨平臺的圖形API,屬于OpenGL的一個簡化版本。需要注意的是:OpenGL ES是應用程序編程接口,該接口描述了方法、結構、函數應具有的行為以及應該如何被使用的語義。也就是說它只定義了一套規范,具體的實現由設備制造商根據規范去做。
QuartzCore:這個框架的名稱感覺不是很清晰,不明確,但是看它的頭文件可以發現,它其實就是CoreAnimation,封裝的CoreAnimation,這個框架的頭文件只包含了CoreAnimation.h
CoreAnimation:核心動畫,顧名思義,想要做動畫就使用CA開頭的就沒錯。在CoreAnimation框架下,最主要的兩個部分是圖層CALayer以及動畫CAAnimation類。前者管理著一個可以被用來實現動畫的位圖上下文;后者是一個抽象的動畫基類,它提供了對CAMediaTimingCAAction協議的支持,方便子類實例直接作用于CALayer本身來實現動畫效果。
UIView:在iOS當中,所有的視圖都從一個叫做UIVIew的基類派生而來,UIView可以處理觸摸事件,可以支持基于Core Graphics繪圖,可以做仿射變換(例如旋轉或者縮放),或者簡單的類似于滑動或者漸變的動畫。

2. CALayer

什么是Layer
CALayer包含在QuartzCore框架中,這是一個跨平臺的框架,既可以用在iOS中又可以用在Mac OS X中。
CALayerCore Animation操作的對象, 這也是Layer為什么是CA打頭的原因;
CALayer我們又稱為圖層,在每個UIView內部都有一個layer的屬性,UIView之所以能夠顯示,就是因為它里面有layer層,才具有顯示的功能,我們通過操作CALayer對象,可以很方便地調整UIView的一些外觀屬性,例如可以給UIView設置陰影,圓角,邊框等等...

一般來說,layer可以有兩種用途,二者不互相沖突:一是對view相關屬性的設置,包括圓角、陰影、邊框等參數,更詳細的參數請點擊這里;二是實現對view的動畫操控。因此對一個view進行core animation動畫,本質上是對該view的.layer進行動畫操縱。

CALayer和UIView的區別:
UIView之所以能看得見是因為里面有一個圖層(即CALayer對象)。對UIView的位置大小的操作,實際上就是對圖層(即CALayer對象)的操作。可以把圖層看成是沒有事件的UIView,而對應UIView則是這個圖層的控制者。
一般情況下是UIView擁有 CALayerCALayerDelegateUIView
Layer的設計目的是提供視圖的基本可視內容,從而提高動畫的執行效率,除提供可視內容外,Layer不負責視圖的事件響應、內容繪制等工作,同時Layer不能參與到響應者鏈條中。

創建視圖對象時,視圖會自己創建一個層,視圖在繪圖(如drawRect:)時,會將內容畫在自己的層上。當視圖在層上完成繪圖后,系統會將圖層拷貝至屏幕。每個視圖都有一個層,而每個圖層又可以有多個子層。

Layer的基本屬性
1.position跟 anchor point

position:它是用來設置當前的layer在父控件當中的位置的.所以它的坐標原點.以父控件的左上角為(0.0)點.
anchorPoint:又稱錨點,它是決點CALayer身上哪一個點會在position屬性所指的位置。anchorPoint是以當前的layer左上角為原點(0.0),它的取值范圍是0~1,默認位置在中間也就是(0.5,0.5)。想要修改某個控件的位置,我們可以設置它的position點.設置完畢后.layer身上的anchorPoint會自動定到position所在的位置。

2.設置陰影

//默認圖層是有陰影的, 只不過是透明的。1為不透明,0為透明
_RedView.layer.shadowOpacity = 1;
//設置陰影的偏移量
self.imageV.layer.shadowOffset = CGSizeMake(-30, -10);
//設置陰影的圓角
_RedView.layer.shadowRadius =10;
//設置陰影的顏色,把UIKit轉換成CoreGraphics框架,用.CG開頭
_RedView.layer.shadowColor = [UIColor blueColor].CGColor;

3.設置邊框

設置圖層邊框,在圖層中使用CoreGraphics的CGColorRef
//設置邊框的顏色
_RedView.layer.borderColor = [UIColor whiteColor].CGColor;
//設置邊框的寬度
_RedView.layer.borderWidth = 2;

4.設置圓角

圖層的圓角半徑,圓角半徑為寬度的一半, 就是一個圓
_RedView.layer.cornerRadius = 50;

操作layer改變UIImageView的外觀 設置圖形邊框

//設置邊框寬度
_imageView.layer.borderWidth = 2;
//設置邊框顏色
_imageView.layer.borderColor = [UIColor whiteColor].CGColor;
設置圖片的圓角半徑
//我們設置的所有layer的屬性只作用在根層上,根層設置為圓形后,其上面的圖片并不會改變,
因此需要裁剪
_imageView.layer.cornerRadius = 50;
//裁剪,超出裁剪區域的部分全部裁剪掉;如果設置了maskToBounds=YES,那將不會有陰影效果
_imageView.layer.masksToBounds = YES;

注意:UIImageView當中Image并不是直接添加在根層上面的.而是添加在layer當中的contents層里。我們設置層的所有屬性它只作用在根層上面.對contents里面的東西并不起作用。所以我們看不到圖片有圓角的效果。想要讓圖片有圓角的效果.可以把masksToBounds這個屬性設為YES.把就會把超過根層以外的東西都給裁剪掉。masksToBounds方法告訴layer將位于它之下的layer都遮蓋住,這樣會使圓角不被遮,但是這樣會導致陰影效果沒有,可以再添加一個SubLayer,添加陰影。此處可以和UIViewclipToBounds來比較記憶(clipToBoundsyes會使其上的內容包括子視圖不能超出邊界)

photoView.clipsToBounds = YES ;

5. contents 設置寄宿圖
方法1:給contents賦CGImage的值

#pragma mark - 創建layer
UIImage *image = [UIImage imageNamed:@"icon1"];
CALayer *layer = self.view.layer;
layer.contents = (__bridge id)(image.CGImage);
layer.contentsGravity = kCAGravityResizeAspect;
layer.contentsScale = [UIScreen mainScreen].scale;
layer.contentsCenter = CGRectMake(0.45, 0.45, 0.1, 0.1);
#pragma mark - 創建View
- (void)creatView
{
    self.view_test = [[UIView alloc]initWithFrame:CGRectMake(10, 100, 100, 100)];
    self.view_test.backgroundColor = [UIColor redColor];
    self.view_test.layer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"layerTest"].CGImage);
    [self.view addSubview:self.view_test];
}

方法2:直接用Core Graphics直接繪制寄宿圖。
能夠通過繼承UIView并實現-drawRect:方法來自定義繪制。如果你不需要寄宿圖,那就不要創建這個方法了,這會造成CPU資源和內存的浪費,這也是為什么蘋果建議:如果沒有自定義繪制的任務就不要在子類中寫一個空的-drawRect:方法。

方法3:設置view的backgroundColor。

self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"me"]];
**設置圖層內容  (將一個UIImage類型轉換為CGImageRef)<c對象轉換成oc對象>**

6.圖層蒙板
這個屬性本身就是個CALayer類型,有和其他圖層一樣的繪制和布局屬性。它類似于一個子圖層,相對于父圖層(即擁有該屬性的圖層)布局,但是它卻不是一個普通的子圖層。不同于那些繪制在父圖層中的子圖層,mask圖層定義了父圖層的部分可見區域。
圖層A有一個屬性是mask,mask實際上也是一個圖層,該圖層設置為圖層B。mask層工作原理是按照透明度裁剪,只保留非透明部分,所以圖層B并非覆蓋在圖層A上,而是根據圖層B不透明的部分去顯示圖層A。若圖層B是個藍色圓環,而圖層A是個紅色的長方形,那么最終顯示的就是紅色的圓環。

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

7.layer的CATransform3D屬性-圖層形變
x,y,z 分別代表x,y,z軸.
//注意:只有旋轉的時候才可以看出3D的效果.
旋轉:CATransform3DMakeRotation(M_PI, 1, 0, 0);
平移:CATransform3DMakeTranslation(x,y,z)
縮放:CATransform3DMakeScale(x,y,z);
旋轉方法1:

    [UIView animateWithDuration:1.0f animations:^{
        redView.layer.transform =
        CATransform3DMakeRotation(M_PI, 1, 1, 0);
    }];

旋轉方法2:KVC

    [UIView animateWithDuration:1.0f animations:^{
         [redView.layer setValue:@(M_PI)
         forKeyPath:@"transform.rotation"];
    }];

可以通過KVC的方式進行設置屬性,但是CATransform3DMakeRotation的值是一個結構體, 所以要把結構轉成對象.

NSValue *value = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI, 1, 0, 0)];
[_imageView.layer setValue:value forKeyPath:@"transform.scale"];

通過KVC改變屬性
除了直接更改layer的屬性外,也可以用KVC方法更改屬性。
什么時候用KVC?
當需要做一些快速縮放,平移,二維旋轉時用KVC。
CoreAnimation使CAAnimationCALayer繼承于NSKeyValueCoding協議,這個擴展增加了默認的一些keys對于的value,添加的keyPath包括了CGPoint,CGRect,CGSizeCATransform3D類型。意思就是你可以根據任意的keys來設置對應的value,即使這個key不是CALayer公開的屬性,你可以根據下面的方式來設置一個value
比如:

// 快速的進行縮放.后面forKeyPath屬性值不是亂寫的.蘋果文檔當中給了相關的屬性
[_imageView.layer setValue:@0.5 forKeyPath:@"transform.scale"];
// 順時針旋轉45
imageView.layer.transform = CATransform3DMakeRotation(M_PI_4, 0, 0, 1);

隱式動畫:
RootLayer:每一個UIView內部都默認關聯著一個CALayer,這個CALayerRoot Layer(根層),根圖層更多的充當容器的做用,并且UIView的根圖層創建工作完全由iOS負責完成,無法重新創建。注意:在正常情況下,對于View中的RootLayer,UIView默認情況下禁止了layer動畫,但是在animation block中又重新啟用了它們.這便是:當一個屬性在動畫 block之外被改變時沒有動畫,但是當屬性在動畫block內被改變時,就帶上了動畫的原因。

非Root Layer:對于所有的非Root Layer,即手動創建的CALayer對象,都存在著隱式動畫。當對非Root Layer的部分屬性進行相應的修改時,默認會自動產生一些動畫效果,這些屬性稱為Animatable Properties(可動畫屬性),凡是文檔中有Animatable字樣的屬性都是可動畫屬性。
隱式屬性動畫的本質是這些屬性的變動默認隱含了CABasicAnimation動畫實現,例如修改bounds屬性會產生縮放動畫,修改backgroundColor屬性會產生背景色的漸變動畫,修改position屬性會產生平移動畫,隱式動畫的默認時長為1/4秒

CALayer可動畫屬性
列舉常見的Animatable Properties:

bounds:(縮放動畫)用于設置CALayer的寬度和高度。修改這個屬性會產生縮放動畫
backgroundColor:用于設置CALayer的背景色。修改這個屬性會產生背景色的漸變動畫
position:(平移動畫)用于設置CALayer的位置。修改這個屬性會產生平移動畫
opacity:(改變透明度)淡入淡出動畫

注意:CALayer中很少使用frame屬性,因為frame本身不支持隱式動畫,通常使用boundsposition代替.

如何取消隱式動畫?
首先要了解動畫底層是怎么做的.動畫的底層是包裝成一個事務來進行的.什么是事務?很多操作綁定在一起,當這些操作執行完畢后,才去執行下一個操作。
對于獨立CALayer而言,隱式動畫是默認開啟的,只需要更改可動畫屬性就會觸發隱式動畫,但對于UIView的Backing Layer而言,隱式動畫是默認關閉的。
當更改獨立CALayer的可動畫屬性時,Core Animation會為我們創建默認的動畫對象來實現相應的動畫,但Core Animation又以什么作為標準來實現這些動畫呢?隱式動畫的大部份參數由事務(CATransaction)來決定,其中包括,動畫時間,動畫緩沖等。
雖然隱式動畫的大部份參數由CATransaction來決定,但我們奇怪地發現,在代碼中并沒有出現CATransaction,WHY?這是由于CATransaction和NSAutoreleasePool一樣,也分為隱式CATransaction和顯式CATransaction,而CATransaction對隱式動畫的管理方式與NSAutoreleasePool對內存的管理方式也十分相似,[CATransaction begin]方法是CATransaction的開始,而[CATransaction commit]則是CATransaction的結束,中間便是CATransaction的作用域,需要把更改可動畫屬性的操作放在CATransaction的作用域內,Core Animation才會創建相應的隱式動畫。
因此我們自己開啟事務,并在事物中設置沒有動畫就會隱藏動畫了。
可以通過動畫事務(CATransaction)關閉默認的隱式動畫效果

#pragma mark - 關閉默認的隱式動畫效果 
下述代碼為CATransaction的顯式創建
顯式創建CATransaction常常被用于關閉隱式動畫(獨立的CALayer對象默認開啟隱式動畫,需要手動關閉)和調整動畫的時間
// 獲取觸摸對象
UITouch *t = touches.anyObject; 
 // 獲取手指的位置
CGPoint p = [t locationInView:t.view]; 
 // 開啟事務
 [CATransaction begin];
 // 設置事務沒有動畫/禁用隱式動畫 
 [CATransaction setDisableActions:YES]; 
//設置動畫執行的時長
[CATransaction setAnimationDuration:2];
// 更改可動畫屬性(這里是更改位置)
self.layer.position = p; 
// 提交事務
 [CATransaction commit]; 

顯式動畫
一般指在CALayer上進行Core Animation動畫(屬性動畫,轉場動畫,動畫組)。
核心動畫提供了一個顯示動畫模型。該顯式動畫模型需要你創建一個動畫對象,并設置值,顯示動畫不會開始執行,直到你把該動畫應用到某個圖層上面。顯式動畫的基類為CAAnimation,常用的是CABasicAnimation,CAKeyframeAnimation有時候還會使用到CAAnimationGroup,CATransition(注意不是CATransaction,Transition是過渡的意思)。具體看下一篇文章。

presentationLayer與modelLayer
顯式動畫跟修改CALayer內部屬性逐漸變換是不一樣的,顯式動畫修改的是presentationLayer的數據,修改CALayer內部屬性逐漸變換是修改modelLayer的數據。presentationLayerCALayer眼睛看到屏幕上CALayer的位置,而實際上CALayer還在modelLayer位置上。這也就是為什么顯式動畫結束后,如果不使用代碼,CALayer還會回到原來位置,因為modelLayer的數據并沒有修改。

顯隱式動畫區別
不管是顯式動畫還是隱式動畫,都是基于CAAnimation來實現的,所以這兩種動畫形式的主要差異在于,是否顯式創建CAAnimation對象來實現動畫,當然它們還存在其它的差異,例如,隱式動畫是基于CABasicAnimation對來實現的(注意不同點:modelLayer被真正修改了),而顯式動畫可以有更多的選擇,可以是CABasicAnimation,CAKeyframeAnimation,CATransition等。

總結:

  • Core Animation默認對所有東西都做動畫,但是隱式動畫被UIKit禁用掉。
  • UIKit包括事件處理及CALayer,它擁有自己的一些屬性,比如frame,backgroundColor,這些其實是對CALayer的屬性的封裝,其中比較關注的是2d仿射屬性及maskview屬性。我們可以其用封裝好的屬性或者其Layer根圖層的屬性對View進行修改。無論是改變了此view的屬性還是它的layer的屬性,這些都真實改變了此view的數據(modelLayer的數據修改了),而且不會引起動畫。
  • 對于獨立Layer,修改它的屬性,真實的改變了它的modelLayer的數據,部分屬性會有隱式動畫,如果想關閉可以用事務來關閉。其中比較關注的是3d仿射屬性(特別是旋轉-m.34)、Mask圖層蒙版屬性、SubLayer以及貝塞爾曲線;
  • 如果Layer是UIView持有的,對Layer操作就是對UIView的操作。CALayer所擁有的屬性、方法、協議對持有它的UIView同樣有用。

關于動畫
對UIView進行動畫,可以通過block方式,在Block里改變屬性,或者改變Layer根圖層的屬性,可生成動畫;也可以用Core Animation,對其持有的Layer進行核心動畫操作(屬于顯式動畫,更改的是presentationLayer數據)。

對CALayer進行動畫,可以用隱式動畫(),也可以用Core Animation,其實隱式動畫是基于核心動畫中的CABasicAnimation的(注意不同點:modelLayer被真正修改了)、(屬于顯式動畫,更改的是presentationLayer數據)

核心動畫的對象是CALayer,無論是獨立的Layer還是UIView,都可以進行核心動畫操作。

3. 繪圖

使用Core Graphics繪圖的步驟:

  • 獲取上下文(畫布)
  • 創建路徑(自定義或者調用系統的API)并添加到上下文中。
  • 進行繪圖內容的設置(畫筆顏色、粗細、填充區域顏色、陰影、連接點形狀等)
  • 開始繪圖(CGContextDrawPath

繪圖步驟.png

圖形上下文
Core Graphics API所有的操作都在一個上下文中進行。所以在繪圖之前需要獲取該上下文并傳入執行渲染的函數中。獲得一個圖形上下文是我們完成繪圖任務的第一步,你可以將圖形上下文理解為一塊畫布。如果你沒有得到這塊畫布,那么你就無法完成任何繪圖操作。當然,有許多方式獲得一個圖形上下文,這里我介紹兩種最為常用的獲取方法。

UIView的詳細顯示過程
UIView需要顯示時,它內部的圖層會準備好一個CGContextRef(圖形上下文),然后調用delegate(這里就是UIView)的drawLayer:inContext:方法,并且傳入已經準備好的CGContextRef對象。而UIViewdrawLayer:inContext:方法中又會調用自己的drawRect:方法。
平時在drawRect:中通過UIGraphicsGetCurrentContext()獲取的就是由圖層傳入的CGContextRef對象,在drawRect:中完成的所有繪圖都會填入圖層的CGContextRef中,然后被拷貝至屏幕。
view層會調用drawRect:方法從新繪制新的視圖,但是這個方法是用CPU在主線程重新繪制了視圖,會導致內存消耗過大。

什么時候需要繪圖:
所需要的設計樣子,在目前UIKit框架下無法滿足設計圖的情況下,可以自己畫出復雜的樣式。
可以實現復雜的樣式,比如線條、矩形,圓形 橢圓形,不規則圖形、可以賦值文字,賦值圖片等。

獲取&創建圖形上下文的4種方式:

注:優先級-displayLayer: > -drawInContext: > -drawLayer:inContext: > -drawRect:

// 1. UIView,在drawRect中,Cocoa會為你創建一個圖形上下文
- (void)drawRect:(CGRect)rect
// 2. CALayer
- (void)drawInContext:(CGContextRef)ctx
// 3. delegate回調
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
// 4. 創建圖片類型的上下文
UIGraphicsBeginImageContextWithOptions

UIGraphicsBeginImageContextWithOptions函數創建的上下文適用于圖像操作,并且該上下文屬于當前上下文,你也可以通過UIGraphicsGetCurrentContext函數獲得當前圖形上下文
drawRect方法調用時,Cocoa創建的上線屬于當前圖形上下文,你也可以通過UIGraphicsGetCurrentContext函數獲得當前圖形上下文
delegate回調所持有的context,只是對一個圖形上下文的引用,并不一定是當前上下文

一共有4種使用context的場景
場景一:使用UIKit框架下的方法,新建一個文件,繼承UIView,重寫-(void)drawRect:
1.1:UIKit的OC方法

#import "DrawRect_OC.h"
@implementation DrawRect_OC
// 可省略
//- (instancetype)initWithFrame:(CGRect)frame
//{
//    self = [super initWithFrame:frame];
//    if (self) {
//
//
//    }
//    return self;
//}

- (void)drawRect:(CGRect)rect {
    [super drawRect:rect];
    //繪制出圓了
    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:rect];
    //設置填充顏色
    UIColor *fillColor = [UIColor redColor];
    [fillColor set];
    [path fill];
    //設置畫筆顏色
    UIColor *stokeColor = [UIColor greenColor];
    [stokeColor set];
    [path stroke];
}

1.2:使用Core Graphics框架下的方法:

#import "DrawRect_CG.h"

@implementation DrawRect_CG

- (void)drawRect:(CGRect)rect
{
    [super drawRect:rect];
    //獲取ctx
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    //設置畫圖相關樣式參數
    
    //設置筆觸顏色
    CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);
    //設置筆觸寬度
    CGContextSetLineWidth(ctx, 2);
    //設置填充色
    CGContextSetFillColorWithColor(ctx, [UIColor greenColor].CGColor);
    //設置拐點樣式
    //    enum CGLineJoin {
    //        kCGLineJoinMiter, //尖的,斜接
    //        kCGLineJoinRound, //圓
    //        kCGLineJoinBevel //斜面
    //    };
    CGContextSetLineJoin(ctx, kCGLineJoinRound);
    //Line cap 線的兩端的樣式
    //    enum CGLineCap {
    //        kCGLineCapButt,
    //        kCGLineCapRound,
    //        kCGLineCapSquare
    //    };
    CGContextSetLineCap(ctx, kCGLineCapRound);
    
    
    [self drawSharpWithctx:ctx Rect:rect];//畫矩形、橢圓形、多邊形
//    [self drawPictureWithctx:ctx Rect:rect];//畫圖片
//    [self drawTextWithctx:ctx Rect:rect];//畫文字

    
    
}

#pragma mark - 畫矩形、橢圓形、多邊形
-(void)drawSharpWithctx:(CGContextRef)ctx Rect:(CGRect)rect{
    
//    //畫橢圓,如果長寬相等就是圓
    CGContextAddEllipseInRect(ctx, rect);
    
    
//    //畫矩形,長寬相等就是正方形
//    CGContextAddRect(ctx, CGRectMake(5, 5, rect.size.width-5-5, rect.size.width-5-5));
    
    
    //畫多邊形,多邊形是通過path完成的
//    CGMutablePathRef path = CGPathCreateMutable();
//    CGPathMoveToPoint(path, &CGAffineTransformIdentity, 1, 1);
//    CGPathAddLineToPoint(path, &CGAffineTransformIdentity, rect.size.width-1-1, 1);
//    CGPathAddLineToPoint(path, &CGAffineTransformIdentity, rect.size.width-1-1, rect.size.width-1-1);
//    CGPathCloseSubpath(path);
//    CGContextAddPath(ctx, path);
    
    //填充
    CGContextFillPath(ctx);
}


#pragma mark - 畫圖片
-(void)drawPictureWithctx:(CGContextRef)ctx Rect:(CGRect)rect
{
    /*圖片*/
    UIImage *image = [UIImage imageNamed:@"layerTest"];
    [image drawInRect:rect];//在坐標中畫出圖片
}

#pragma mark - 畫文字
-(void)drawTextWithctx:(CGContextRef)ctx Rect:(CGRect)rect
{
    //文字樣式
    UIFont *font = [UIFont systemFontOfSize:18];
    NSDictionary *dict = @{NSFontAttributeName:font,
                           NSForegroundColorAttributeName:[UIColor whiteColor]};
    [@"hello world" drawInRect:rect withAttributes:dict];
}

畫矩形、橢圓形、多邊形

#pragma mark - 畫矩形、橢圓形、多邊形
-(void)drawSharpWithctx:(CGContextRef)ctx Rect:(CGRect)rect{
    
//    //畫橢圓,如果長寬相等就是圓
    CGContextAddEllipseInRect(ctx, rect);
    
    
//    //畫矩形,長寬相等就是正方形
//    CGContextAddRect(ctx, CGRectMake(5, 5, rect.size.width-5-5, rect.size.width-5-5));
    
    
    //畫多邊形,多邊形是通過path完成的
//    CGMutablePathRef path = CGPathCreateMutable();
//    CGPathMoveToPoint(path, &CGAffineTransformIdentity, 1, 1);
//    CGPathAddLineToPoint(path, &CGAffineTransformIdentity, rect.size.width-1-1, 1);
//    CGPathAddLineToPoint(path, &CGAffineTransformIdentity, rect.size.width-1-1, rect.size.width-1-1);
//    CGPathCloseSubpath(path);
//    CGContextAddPath(ctx, path);
    
    //填充
    CGContextFillPath(ctx);
}

畫圖片

#pragma mark - 畫圖片
-(void)drawPictureWithctx:(CGContextRef)ctx Rect:(CGRect)rect
{
    /*圖片*/
    UIImage *image = [UIImage imageNamed:@"layerTest"];
    [image drawInRect:rect];//在坐標中畫出圖片
}

畫文字

#pragma mark - 畫文字
-(void)drawTextWithctx:(CGContextRef)ctx Rect:(CGRect)rect
{
    //文字樣式
    UIFont *font = [UIFont systemFontOfSize:18];
    NSDictionary *dict = @{NSFontAttributeName:font,
                           NSForegroundColorAttributeName:[UIColor whiteColor]};
    [@"hello world" drawInRect:rect withAttributes:dict];
}

場景二:在drawLayer:inContext:代理方法中實現操作

@interface ViewController ()<CALayerDelegate> //遵守協議

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];    
    //在自定義Layer中:- (void)drawInContext:(CGContextRef)ctx
//    [self drawInContext_CG];//注意:setNeedsDisplay
}
#pragma mark - 代理方法 
- (void)drawLayer_CG_VC
{
    CALayer *layer = [CALayer layer];
    layer.bounds = CGRectMake(0, 0, 100, 100);
    layer.position = CGPointMake(K_width/2, K_height/2);
    layer.backgroundColor = [UIColor blueColor].CGColor;
    layer.delegate = self; //設置代理
    [layer setNeedsDisplay];// 調用此方法,drawLayer: inContext:方法才會被調用。
    [self.view.layer addSublayer:layer];
}
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
    //使用Core Graphics實現-CG-1
    // 1.畫一個圓
//    CGContextAddEllipseInRect(ctx, CGRectMake(0, 0, 50, 50));
//    // 填充顏色為紅色
//    CGContextSetRGBFillColor(ctx, 1, 0, 0, 1);
//    // 在context上繪制
//    CGContextFillPath(ctx);
    
    //使用Core Graphics實現-CG-2
    // 2.畫一個橢圓
//    CGContextAddEllipseInRect(ctx, CGRectMake(0,0,100,100));
//    //填充顏色為藍色
//    CGContextSetFillColorWithColor(ctx, [UIColor blueColor].CGColor);
//    //在context上繪制
//    CGContextFillPath(ctx);
    
    // 使用UIKit實現-1
    //使用UIKit進行繪制,因為UIKit只會對當前上下文棧頂的context操作,所以要把形參中的context設置為當前上下文
//    UIGraphicsPushContext(ctx);
//    UIImage* image = [UIImage imageNamed:@"test.png"];
//    //指定位置和大小繪制圖片
//    [image drawInRect:CGRectMake(0, 0,100 , 100)];
//    UIGraphicsPopContext();
}

場景三:使用自定義圖層繪制,直接繪制到圖層。
編寫一個類繼承于CALayer然后在drawInContext:中使用CGContext繪圖。同樣需要調用setNeedDisplay方法。此方法由于并非UIKit直接調用,因此只能用原生的Core Graphics方法繪制。

#import "CustomLayer_CG.h"

@implementation CustomLayer_CG
- (void)drawInContext:(CGContextRef)ctx
{
    // 紅色
    CGContextSetRGBFillColor(ctx, 1, 0, 0, 1);
    // 添加圓
    CGContextAddEllipseInRect(ctx, CGRectMake(0, 0, 50, 50));
    // 實心繪制
    CGContextFillPath(ctx);
}
@end
@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor]; 
    //在自定義Layer中:- (void)drawInContext:(CGContextRef)ctx
//    [self drawInContext_CG];//注意:setNeedsDisplay
}
#pragma mark -  drawInContext
- (void)drawInContext_CG
{
    CustomLayer_CG *layer = [CustomLayer_CG layer];
    layer.bounds = CGRectMake(0, 0, 100, 100);
    layer.position = CGPointMake(K_width/2, K_height/2);
    layer.backgroundColor = [UIColor blueColor].CGColor;
    // 有這句話才能執行 -drawInContext 和 drawRect 方法
    [layer setNeedsDisplay];
    [self.view.layer addSublayer:layer];
}

場景四:通過自己創建一個context來繪制,通常用于對圖片的處理。
4.1:使用UIKit實現:
解釋一下UIGraphicsBeginImageContextWithOptions函數參數的含義:第一個參數表示所要創建的圖片的尺寸;第二個參 數用來指定所生成圖片的背景是否為不透明,如上我們使用YES而不是NO,則我們得到的圖片背景將會是黑色,顯然這不是我想要的;第三個參數指定生成圖片 的縮放因子,這個縮放因子與UIImage的scale屬性所指的含義是一致的。傳入0則表示讓圖片的縮放因子根據屏幕的分辨率而變化,所以我們得到的圖 片不管是在單分辨率還是視網膜屏上看起來都會很好。

UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100), NO, 0);
UIBezierPath* p = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,100,100)];
[[UIColor blueColor] setFill];
[p fill];
UIImage* image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

4.2:使用Core Graphics實現:

    //該函數會自動創建一個context,并把它push到上下文棧頂,坐標系也經處理和UIKit的坐標系相同
    UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextAddEllipseInRect(context, CGRectMake(0,0,100,100));
    //填充顏色為藍色
    CGContextSetFillColorWithColor(context, [UIColor blueColor].CGColor);
    //在context上繪制
    CGContextFillPath(context);
    //把當前context的內容輸出成一個UIImage圖片
    UIImage* image = UIGraphicsGetImageFromCurrentImageContext();
    //上下文棧pop出創建的context
    UIGraphicsEndImageContext();
    [image drawInRect:CGRectMake(0, 0, 100, 100)];
4. 貝塞爾曲線-UIBezierPath

繪圖模塊中,知道了不使用圖片的情況下用CGPath去構造任意形狀的陰影。如果我們能用同樣的方式創建相同形狀的圖層就好了。
使用UIBezierPath類可以創建基于矢量的路徑,這個類在UIKit中。此類是Core Graphics框架關于path的一個封裝。使用此類可以定義簡單的形狀,如橢圓或者矩形,或者有多個直線和曲線段組成的形狀。
UIBezierPath對象是CGPathRef數據類型的封裝。path如果是基于矢量形狀的,都用直線和曲線段去創建。我們使用直線段去創建矩形和多邊形,使用曲線段去創建弧(arc),圓或者其他復雜的曲線形狀。
每一段都包括一個或者多個點,繪圖命令定義如何去詮釋這些點。每一個直線段或者曲線段的結束的地方是下一個的開始的地方。
每一個連接的直線或者曲線段的集合成為subpath。一個UIBezierPath對象定義一個完整的路徑包括一個或者多個subpaths。

(1)創建一個Bezier path對象。
(2)使用方法moveToPoint:去設置初始線段的起點。
(3)添加line或者curve去定義一個或者多個subpaths,添加線或者曲線去定義一個或者多個子路徑
(4)改變UIBezierPath對象跟繪圖相關的屬性。

CAShapeLayer與UIBezierPath的關系:

  • 1.CAShapeLayer中shape代表形狀的意思,所以需要形狀才能生效
  • 2.貝塞爾曲線可以創建基于矢量的路徑,而UIBezierPath類是對CGPathRef的封裝
  • 3.貝塞爾曲線給CAShapeLayer提供路徑,CAShapeLayer在提供的路徑中進行渲染。路徑會閉環,所以繪制出了Shape
  • 4.用于CAShapeLayer的貝塞爾曲線作為path,其path是一個首尾相接的閉環的曲線,即使該貝塞爾曲線不是一個閉環的曲線.
    如何在實踐使用:
    第一種寫在drawRect中
#import "PathViewByBezier.h"

@implementation PathViewByBezier

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        self.backgroundColor = [UIColor redColor];
    }
    return self;
}

- (void)drawRect:(CGRect)rect {
    [super drawRect:rect];
    [self drawCiclePath];
}

- (void)drawCiclePath {
    // 傳的是正方形,因此就可以繪制出圓了
    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(20, 20, self.frame.size.width - 40, self.frame.size.width - 40)];
    
    // 設置填充顏色
    UIColor *fillColor = [UIColor greenColor];
    [fillColor set];
    [path fill];
    
    // 設置畫筆顏色
    UIColor *strokeColor = [UIColor blueColor];
    [strokeColor set];

    // 根據我們設置的各個點連線
    [path stroke];
}
@end
@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self creatPathView];
}

- (void)creatPathView
{
    PathViewByBezier *pathView = [[PathViewByBezier alloc]initWithFrame:CGRectMake(0, 100, 100, 100)];
    [self.view addSubview:pathView];
}

第二種在接下來講到的CAShapeLayer中一塊使用.(使用CAShapeLayer與貝塞爾曲線可以實現不在view的DrawRect方法中畫出一些想要的圖形)**

- (void)setShapeLayer {
    
    /*
    //   創建折線
    UIBezierPath *path = [UIBezierPath bezierPath];
    //  添加路徑起點
    [path moveToPoint:(CGPoint){20,20}];
    // 添加路徑之外的其他點
    [path addLineToPoint:(CGPoint){160,160}];
    [path addLineToPoint:(CGPoint){180,50}];
    [path closePath];  // 關閉路徑  添加一個結尾和起點相同的點
     */
    
    
    /*
    //創建圓
    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:(CGRect){0,0,100,100}];
     */

    
     /*
    //創建橢圓
    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:(CGRect){0,0,180,100}];
      */

    
     /*
    //創建矩形
    UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(10, 10, 100, 80)];
      */

    
     /*
    //創建圓角矩形
    UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:(CGRect){0,0,200,200} cornerRadius:30];
      */
    

    /*
    //畫單角的圓角矩形
    UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:(CGRect){0,0,200,200} byRoundingCorners:UIRectCornerTopLeft cornerRadii:(CGSize){30,0}];
     */
    
    
    /*
    //圓弧
     Center : 圓點
     radius :半徑
     startAngle :開始畫弧的起始角度; 0度:右 90度:下 180度;左 270度:上
     endAngle :終點畫弧的終點角度
     M_PI : 轉化為180度角
     M_PI_2 : 轉化為90度角
     clockwise : 是否為順時針方向
     
    UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(100, 100) radius:100 startAngle:M_PI_2+M_PI endAngle:M_PI clockwise:YES];
    */
    
    
    /*
    //折線和弧線構成的曲線
    //創建路徑
    UIBezierPath *path = [UIBezierPath bezierPath];
    //折線
    [path moveToPoint:(CGPoint){0,0}];
    [path addLineToPoint:CGPointMake(100, 100)];
     //添加一條弧線:在原有的線上添加一條弧線
    [path addArcWithCenter:CGPointMake(100, 100) radius:50 startAngle:0 endAngle:M_PI clockwise:YES];
    */

    
    /*
    //二次貝塞爾曲線
     moveToPoint :起點
     ToPoint : 終點
     controlPoint : 控制點
     曲線是由起點趨向控制點最后到達終點(不會經過控制點)的曲線。控制點決定曲線的起始方向,起點和終點的距離決定曲線趨向控制點的程度
    UIBezierPath *path = [UIBezierPath bezierPath];
    
    [path moveToPoint:(CGPoint){0,150}];
    [path addQuadCurveToPoint:(CGPoint){200,200} controlPoint:(CGPoint){100,0}];
     */
    
    /*

     //三次貝塞爾曲線
     UIBezierPath *path = [UIBezierPath bezierPath];
     [path moveToPoint:(CGPoint){0,150}];
     [path addCurveToPoint:(CGPoint){200,50} controlPoint1:CGPointMake(50, 75) controlPoint2:CGPointMake(150, 125)];
    */

    UIBezierPath *path = [UIBezierPath bezierPath];

    //  設置路徑畫布
    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    shapeLayer.backgroundColor = [UIColor yellowColor].CGColor;
    shapeLayer.frame = (CGRect){50,100,200,200};
    shapeLayer.lineWidth = 2.0;
    shapeLayer.strokeColor = [UIColor orangeColor].CGColor; //   邊線顏色
    
    shapeLayer.path = path.CGPath;
    shapeLayer.fillColor  = [UIColor greenColor].CGColor;   //  填充顏色,默認是black
    
    //  添加到圖層上
    [self.view.layer addSublayer:shapeLayer];
//    self.view.layer.mask = shapeLayer;  // layer 的 mask屬性,添加蒙版

}
5. CALayer子類

** 形狀圖層CAShapeLayer** - 繪制不規則圖形
CAShapeLayer繼承自CALayer,因此,可使用CALayer的所有屬性。而單獨使用CAShapeLayer是沒有任何意義的。
使用CAShapeLayer與貝塞爾曲線可以實現不在view的DrawRect方法中畫出一些想要的圖形,UIBezierPath只是告訴路徑給CAShapeLayer,具體這個shape什么樣子由CAShapeLayer來決定。
CAShapeLayer可以用來繪制所有能夠通過CGPath來表示的形狀。這個形狀不一定要閉合,圖層路徑也不一定要不可破,事實上你可以在一個圖層上繪制好幾個不同的形狀。
你也可以用Core Graphics直接向原始的CALyer的內容中繪制一個路徑,相比直下,使用CAShapeLayer有以下一些優點:
CAShapeLayer和drawRect的比較(可與上述代碼聯系到一塊)

  • 1.drawRect:屬于CoreGraphics框架,占用CPU,性能消耗大
  • 2.CAShapeLayer:屬于CoreAnimation框架,通過GPU來渲染圖形,節省性能。動畫渲染直接提交給手機GPU,不消耗內存.
    主要屬性
// CAShapeLayer 繪制的路徑
@property CGPathRef path;表示路徑,可以用貝塞爾曲線,也可以自定義路徑
//路徑中的填充顏色
@property(nullable) CGColorRef fillColor;//無動畫
//畫筆顏色(路徑的顏色,邊框顏色)
@property(nullable) CGColorRef strokeColor; //有動畫
//這是一組范圍值,路徑繪制開始和結束的范圍(0 -> 1)
@property CGFloat strokeStart;
@property CGFloat strokeEnd;
//以下屬性參見 UIBezierPath 的介紹
@property CGFloat lineWidth;線寬,用點表示單位
@property CGFloat miterLimit;
@property(copy) NSString lineCap;線條結尾的樣子
@property(copy) NSString *lineJoin;線條之間的結合點的樣子
//填充規則
@property(copy) NSString fillRule;
//設置虛線顯示的起點距離,設置為8,則顯示長度8之后的線
@property CGFloat lineDashPhase;
//設置虛線線段的長度和空格的長度,@[@20,@30,@40,@50],畫20空30畫40空50
@property(nullable, copy) NSArray<NSNumber > lineDashPattern;
- (void)creatshapeLayer
{
    CALayer *layer =[CALayer layer] ;
    //設置layer的frame,會改變貝塞爾曲線的frame,它根據layer的frame而改變,也就是說它作為子layer存在(在坐標系統上是存在這種父子關系的,其他不是)
    layer.frame = CGRectMake(50, 250, 150, 150);
    layer.backgroundColor = [UIColor purpleColor].CGColor;
    [self.view.layer addSublayer:layer];
    
    self.shapeLayer =[CAShapeLayer layer] ;
    self.shapeLayer.frame = CGRectMake(0, 0, 150, 150);
    [layer addSublayer:self.shapeLayer];
    
    // bezierPathWithOvalInRect:xy坐標也是從左上開始,150不是半徑!是寬度跟高度
    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(75/2, 75/2, 75, 75)];
    
    self.shapeLayer.path = path.CGPath;
    //fillcolor沒有動畫;strokeColor有動畫,這樣只能在strokeColor上考慮動畫了
    self.shapeLayer.fillColor = [UIColor clearColor].CGColor;
    //重點在線寬
    self.shapeLayer.lineWidth = 75;
    self.shapeLayer.strokeColor = [UIColor redColor].CGColor;
    
    layer.mask = self.shapeLayer;
    
    CABasicAnimation *anmation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    anmation.fromValue = @0.0;
    anmation.toValue = @1;
    anmation.duration = 2.5;
    anmation.repeatCount = MAXFLOAT;
    [self.shapeLayer addAnimation:anmation forKey:@""];
/*    
   總結:frame的影響
    mask父影響-》masklayer影響-》貝塞爾曲線 -進而決定了shapelayer的最終形狀。
    不過要處理好,我們不能創建的CAShapeLayer的frame小于貝塞爾曲線 因為如果這樣只要設置了masksToBounds這個屬性會把超出部分截掉。
*/
}

** 漸變圖層CAGradientLayer - 顏色漸變、陰影**
繼承calayer,主要用于處理顏色漸變的圖層。CAGradientLayer作為CALayer的子類,唯一的用途就是用來做顏色漸變,梯度動畫效果等。

1.CAGradientLayer是用于處理漸變色的層結構
,漸變色可以做隱式動畫.

2.大部分情況下, CAGradientLayer都是和CAShapeLayer相互配合使用(CAShapeLayer提供形狀, CAGradientLayer提供背景).
3.CAGradientLayer可以用作png圖片的遮罩效果.

CAGradientLayer的**坐標系統
**

a. CAGradientLayer的坐標系統是從坐標(0, 0) 到(1, 1)繪制的矩形

b. CAGradientLayer的frame的值的size不為正方形的話, 坐標系統會被拉伸

c. CAGradientLayer的startPoint與endPoint會直接影響顏色的繪制方向

d. CAGradientLayer的顏色分割點是以0到1的比例來計算的

#pragma mark - 創建gradientLayer 顏色漸變-滑動解鎖

/*
 文字漸變實現思路:
 
 1.創建一個顏色漸變層,漸變圖層跟文字控件一樣大。
 
 2.用文字圖層裁剪漸變層,只保留文字部分,就會讓漸變層只保留有文字的部分,相當于間接讓漸變層顯示文字,我們看到的其實是被裁剪過后,漸變層的部分內容。
 
 注意:如果用文字圖層裁剪漸變層,文字圖層就不在擁有顯示功能,這個圖層就被弄來裁剪了,不會顯示,在下面代碼中也會有說明。
 
 2.1 創建一個帶有文字的label,label能顯示文字。
 
 2.2 設置漸變圖層的mask為label圖層,就能用文字裁剪漸變圖層了。
 
 3.mask圖層工作原理: 根據透明度進行裁剪,只保留非透明部分,顯示底部內容。
 */
- (void)creatgradientLayer
{
    self.view.backgroundColor=[UIColor grayColor];
    
    CAGradientLayer *gradientLayer=[CAGradientLayer layer];
    
    gradientLayer.frame=CGRectMake(30, 200, 200, 66);
    
    //顏色分布范圍--漸變顏色的數組
//    gradientLayer.colors=@[
//                          
//                          (__bridge id)[UIColor blackColor].CGColor,
//                          
//                          (__bridge id)[UIColor whiteColor].CGColor,
//                          
//                          (__bridge id)[UIColor blackColor].CGColor
//                          
//                          ];
    gradientLayer.colors=@[
                          
                          (__bridge id)[UIColor redColor].CGColor,
                          
                          (__bridge id)[UIColor purpleColor].CGColor,
                          
                          (__bridge id)[UIColor cyanColor].CGColor
                          
                          ];

    //設置好 colors 要設置好與之相對應的 locations 值

    gradientLayer.locations=@[@.25,@.5,@.75];
    //CAGradientLayer 默認的漸變方向是從上到下,即垂直方向。
    //映射locations中第一個位置,用單位向量表示,比如(0,0)表示從左上角開始變化。默認值是(0.5,0.0)
    //映射locations中最后一個位置,用單位向量表示,比如(1,1)表示到右下角變化結束。默認值是(0.5,1.0)。
    //如果要改變 CAGradientLayer 的漸變方向,則要顯式的給 startPoint和 endPoint 兩個屬性賦值。兩個屬性共同決定了顏色漸變的方向,如果要改為水平方向,則需要改成:

    gradientLayer.startPoint=CGPointMake(0, 0.5);
    
    gradientLayer.endPoint=CGPointMake(1., 0.5);
    
    [self.view.layer addSublayer:gradientLayer];

    
    
    //label文字:文字是模型 提供輪廓
    
    UILabel *label=[[UILabel alloc]initWithFrame:gradientLayer.bounds];
    // 疑問:label只是用來做文字裁剪,能否不添加到view上。
    // 必須要把Label添加到view上,如果不添加到view上,label的圖層就不會調用drawRect方法繪制文字,也就沒有文字裁剪了。
    // 如何驗證,自定義Label,重寫drawRect方法,看是否調用,發現不添加上去,就不會調用
    [self.view addSubview:label];
    
    label.alpha=0.5;
    
    label.text=@"滑動解鎖 >>";
    
    label.textAlignment=NSTextAlignmentCenter;
    
    label.font=[UIFont boldSystemFontOfSize:30];
    // mask層工作原理:按照透明度裁剪,只保留非透明部分,文字就是非透明的,因此除了文字,其他都被裁剪掉,這樣就只會顯示文字下面漸變層的內容,相當于留了文字的區域,讓漸變層去填充文字的顏色。
    // 設置漸變層的裁剪層  注意:一旦把label層設置為mask層,label層就不能顯示了,會直接從父層中移除,然后作為漸變層的mask層
    gradientLayer.mask=label.layer;
    
    // 添加色變動畫:將keyPath賦值為“locations”是讓CAGradientLayer的locations屬性做動畫,因為locations對應著顏色,那么顏色也會跟著動,最終的顯示效果就是:
    CABasicAnimation *theAnima=[CABasicAnimation animationWithKeyPath:@"locations"];
    
    theAnima.fromValue=@[@0,@0,@0.25];
    
    theAnima.toValue=@[@0.25,@1,@1];
    
    theAnima.duration=2.5;
    
    theAnima.repeatCount=HUGE;

    [gradientLayer addAnimation:theAnima forKey:@"locations"];
    
}

復制圖層 CAReplicatorLayer - 迭代復制同一個圖層
CAReplicatorLayer能復制子層。也就是說,想要復制層,必須先讓這個層成為CAReplicatorLayer的子層sublayer。
CAReplicatorLayer可以對它自己的子Layer進行復制操作。創建了CAReplicatorLayer實例后,設置了它的尺寸大小、位置、錨點位置、背景色,并且將它添加到了replicatorAnimationView的Layer中。

instanceCount: 要創建的副本個數(默認一個),復制出來的層的個數,包括自己.但是復制子層形變(不包括原生子層),
**preservesDepth: ** 是否將3D例子系統平面化到一個圖層(默認NO)
instanceDelay: 動畫時間延遲,以造成動畫的效果。默認為0。復制出來的層的動畫相對前一個延遲0.5秒。
instanceTransform: 迭代圖層的位置 CATransform3D對象(創建方法用:CATransform3DMakeRotation圓形排列,CATransform3DMakeTranslation水平排列)。可以是位移,旋轉,縮放。復制出來的層相對上一個做操作。子layer相對于前一個復制layer的形變屬性。 變換是逐步增加的,每個實例都是相對于前一實例布局。這就是為什么這些復制體最終不會出現在同意位置上.
instanceColor: 子層顏色,會和原生子層背景色沖突,因此二者選其一設置。
instanceRedOffset,instanceGreenOffset,instanceBlueOffset,instanceAlphaOffset: 設置顏色通道偏移量,相對上一個。

注意:
1.一定要設置CAReplicatorLayer的frame,因為instanceTransform屬性都是根據frame的center作為基準的。
2.復制圖層有一個關鍵的地方是,你往原始的圖層中添加動畫效果,復制出來的副本可以實時的復制這些動畫效果。

#pragma mark - 創建replicatorLayer  - 4 - 正方形移動的等待指示器
- (void)creatreplicatorLayer_four
{
    CGFloat radius = 15;
    CGFloat transX = radius + 5;
    CAShapeLayer *baseLayer = [CAShapeLayer layer];
    baseLayer.frame = CGRectMake(0, 100, radius, radius);
    baseLayer.fillColor = [UIColor redColor].CGColor;
    baseLayer.path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, radius, radius)].CGPath;
    
    //橫向創建5個圓形baseLayer
    CAReplicatorLayer *replicator1 = [CAReplicatorLayer layer];
    replicator1.frame = CGRectMake(100, 100, radius, radius);
    replicator1.instanceCount = 5;
    replicator1.instanceTransform = CATransform3DMakeTranslation(transX, 0, 0);
    replicator1.instanceDelay = 0.3; //這個屬性很重要,設置延遲時間才會出現逐個變化的效果
    [replicator1 addSublayer:baseLayer];
    
    //將上面創建的replicator1 作為子視圖在縱向創建5個,形成一個正方形。
    CAReplicatorLayer *replicator2 = [CAReplicatorLayer layer];
    replicator1.frame = CGRectMake(100, 100+radius+10, radius, radius);
    replicator2.instanceCount = 5;
    replicator2.instanceTransform = CATransform3DMakeTranslation(0, transX, 0);
    replicator2.instanceDelay = 0.3;
    [replicator2 addSublayer:replicator1];
    [self.view.layer addSublayer:replicator2];
    
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform"];
    animation.duration = 1;
    animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1, 1, 0)];
    animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.2, 0.2, 0)];
    animation.repeatCount = HUGE;
    animation.autoreverses = YES; //autoreverses需要注意,他的意思是動畫結束時執行逆動畫
    [baseLayer addAnimation:animation forKey:nil];
}
#pragma mark - 創建replicatorLayer - 5 - 三角
- (void)creatreplicatorLayer_five
{
    /** 子layer實現 */
    CAShapeLayer *shape = [CAShapeLayer layer];
    shape.frame = CGRectMake(0, 0, 25, 25);
    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, 25, 25)];
    shape.path = path.CGPath;
    shape.fillColor = [UIColor blackColor].CGColor;

    /** CAReplicatorLayer的實現 */
    CAReplicatorLayer *replicatorLayer = [[CAReplicatorLayer alloc] init];
    replicatorLayer.frame = CGRectMake(100, 100, 25, 25);
    replicatorLayer.backgroundColor = [UIColor cyanColor].CGColor;
    replicatorLayer.instanceDelay = 0.0;    // 動畫延遲秒數
    replicatorLayer.instanceCount = 3;    // 子layer復制數
    //這里面要設置復制的layer的變換了。先向右移動一定像素,再順時針移動一定角度。這里有個點,至少我當時不是很明白,就是為什么這么設置就會閉合成一個三角形。

    CATransform3D t = CATransform3DTranslate(CATransform3DIdentity, 50, 0, 0);
    t = CATransform3DRotate(t ,120 / 180.0 * M_PI, 0, 0, 1);
    replicatorLayer.instanceTransform = t;
    replicatorLayer.instanceAlphaOffset = -0.4;    // 設置透明系數是方便看復制layer的層次
    [replicatorLayer addSublayer:shape];
//    replicatorLayer.masksToBounds = YES;
    [self.view.layer addSublayer:replicatorLayer];
    
    
    //動畫1- 縮放動畫
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform"];
    animation.duration = 2;
    animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1, 1, 0)];
    animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.2, 0.2, 0)];
    animation.repeatCount = HUGE;
    animation.autoreverses = YES; //autoreverses需要注意,他的意思是動畫結束時執行逆動畫
    [shape addAnimation:animation forKey:nil];
    
    
    //動畫2- 旋轉-沒做完
    CATransform3D transform = CATransform3DIdentity;

    CABasicAnimation *animation1 = [CABasicAnimation animationWithKeyPath:@"instanceTransform"];
    animation1.duration = 2;
//    animation1.fromValue = [NSValue valueWithCATransform3D:CATransform3DRotate(transform, M_PI / 5.0, 0, 0, 1)];

    animation1.toValue = [NSValue valueWithCATransform3D:CATransform3DRotate(transform, M_PI / 5.0, 0, 0, 1)];
    animation1.repeatCount = HUGE;
    animation1.autoreverses = YES;
    [shape addAnimation:animation1 forKey:nil];
}

CAEmitterLayer - 粒子動畫

- (void)setEmitterLayer
{
//創建一個CAEmitterLayer
    CAEmitterLayer *snowEmitter = [CAEmitterLayer layer];
    //指定發射源的位置   
    snowEmitter.emitterPosition = CGPointMake(self.view.bounds.size.width / 2.0, -10);
   //指定發射源的大小 
    snowEmitter.emitterSize  = CGSizeMake(self.view.bounds.size.width, 0.0);
   //指定發射源的形狀 和 模式
    snowEmitter.emitterShape = kCAEmitterLayerLine;
    snowEmitter.emitterMode  = kCAEmitterLayerOutline;
    //創建CAEmitterCell
    CAEmitterCell *snowflake = [CAEmitterCell emitterCell];
    //每秒多少個
    snowflake.birthRate = 3.0;
    //存活時間
    snowflake.lifetime = 50.0;
    //初速度
    snowflake.velocity = 10;//因為動畫屬于落體效果,所以我們只需要設置它在 y 方向上的加速度就行了。
   //初速度范圍
    snowflake.velocityRange = 5;
   //y方向的加速度
    snowflake.yAcceleration = 2;
    snowflake.emissionRange = 0;
    snowflake.contents  = (id) [[UIImage imageNamed:@"037"] CGImage];
   //縮小
    snowflake.scale = 0.5;
    snowEmitter.emitterCells = [NSArray arrayWithObject:snowflake];
}

想要手動控制粒子動畫的開始和結束,我們必須通過 KVC 的方式設置 cell 的值才行。以下為代碼

開始
CAEmitterLayer 根據自己的 emitterCells 屬性找到名叫 explosion 的 cell,并設置它的 birthRate 為 500。從而間接地控制了動畫的開始。

[self.explosionLayer setValue:@500 forKeyPath:@"emitterCells.explosion.birthRate"]; 

結束

[self.explosionLayer setValue:@0 forKeyPath:@"emitterCells.explosion.birthRate"]; 

其他比較常用的還有CATransformLayer(3D展示)、CATextLayer(文字動態展示)等

6. 補充:iOS角度與弧度轉換

在iOS中圖片的旋轉單位為弧度而不是角度,所以經常會在兩者之間進行轉換。
角度弧度定義
“ 弧度”和“度”是度量角大小的兩種不同的單位。
兩條射線從圓心向圓周射出,形成一個夾角和夾角正對的一段弧。當這段弧長正好等于圓的半徑時,兩條射線的夾角大小為1弧度。

參考系.png

比如要旋轉90°,就設置此參數為M_PI_2,M_PI_2是π/2。
比如要旋轉180°,就設置此參數為M_PI,M_PI是π。
比如要旋轉360°,就設置此參數為M_PI2,M_PI2是2π。

角度轉弧度
弧度 = 角度/ 180.0 * M_PI
#define DEGREES_TO_RADIANS(angle) ((angle) / 180.0 * M_PI)
弧度轉角度
角度 = 弧度 * 180.0 / M_PI
#define RADIANS_TO_DEGREES(radians) ((radians) * (180.0 / M_PI))
7. 附圖:
CALayer樹狀結構.png
CALayer屬性.png
寄宿圖&上下文.png
CALayer子類.png
Quarts 2D.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,363評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,497評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,305評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,962評論 1 311
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,727評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,193評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,257評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,411評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,945評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,777評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,978評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,519評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,216評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,657評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,960評論 2 373

推薦閱讀更多精彩內容