核心動畫 Core Animation(一、Layer基礎)

核心動畫(Core Animation)是iOS和OS X上圖形渲染和動畫的基礎設施,用于為應用程序的視圖和其他視覺元素設置動畫。核心動畫本身不是繪圖系統。它是用于在硬件中合成和操縱應用程序內容的基礎設施。這種基礎設施的核心是層(layer)對象,用于管理和操縱您的內容。一個圖層將您的內容捕獲到位圖中,這些位圖可以通過圖形硬件輕松操作。在大多數應用程序中,圖層用作管理視圖內容的一種方式,但也可以根據需要創建獨立圖層。
?基于layer層的繪圖與傳統的基于視圖的繪圖技術有很大的不同。使用基于視圖的繪圖,對視圖本身的更改通常會導致調用視圖的drawRect:方法以使用新參數重新繪制內容。但是以這種方式繪制是昂貴的,因為它是使用主線程上的CPU完成的。Core Animation可以通過在硬件中操作緩存的位圖來實現相同或相似的效果,從而避免這種代價。

一、關于Layer的幾個重要屬性及方法

CALayer類中屬性和方法有很多,大多數和使用View的方式相同,在此不多贅述,具體可以自行查看CALayer.h文件。下面主要介紹一下比較重要的屬性方法。

1、屬性:
  • position(CGPoint):位置,與view中的center差不多,默認情況是layer的中心。但是會受錨點(anchorPoint)影響而改變。
  • zPosition(CGFloat):layer的position在父類(super layer)上的Z軸分量,默認為零。
  • anchorPoint(CGPoint):錨點,使用的是以自身為參考的單位坐標系,默認為(0.5, 0.5)。當操縱層的屬性position或transform屬性時,錨點的影響最為明顯。position屬性總是相對于圖層的錨點指定,并且對于應用于圖層的任何轉換也會相對于錨點發生。
圖例
  • anchorPointZ(CGFloat):layer的anchorPoint在Z軸上的分量,默認為零。

  • transform(CATransform3D):作用于layer的3D變換。默認是單位矩陣CATransform3DIdentity。

    • 每個layer都有兩個變換矩陣屬性(transform、sublayerTransform),可以使用它們來操縱圖層及其內容。CALayer的transform屬性指定要既適用于層和其嵌入式子層的變換。通常,當要修改圖層本身時,可以使用此屬性。sublayerTransform屬性定義了僅適用于子層的附加變換,最常用于向場景內容添加透視視覺效果。
    • 因為Core Animation值可以在三維中指定,所以每個坐標點有四個值必須乘以四乘四個矩陣(見下圖)。核心動畫提供了一套全面的功能,用于創建縮放,轉換和旋轉矩陣以及進行矩陣比較(具體可見CATransform3D.h文件,里面方法的使用可以參考CATransform3D -> 3D變換
      )。除了使用函數操縱變換之外,Core Animation擴展了鍵值編碼支持,允許使用關鍵路徑修改變換。
  • sublayerTransform(CATransform3D):當將內容呈現到接收器的輸出中時,將3D變換應用于“sublayers”數組的每個成員。 通常用作投影矩陣以將透視和其他觀看效果添加到模型。默認是單位矩陣。

  • sublayers(NSArray<CALayer >):子layer的集合。

  • masksToBounds(BOOL):是否沿著邊界裁剪。

  • contents(id):layer的內容由包含要顯示的視覺數據的位圖組成。您可以通過以下三種方式之一提供該位圖的內容:

    • 直接將圖像對象直接分配給圖層對象的contents屬性。(這種技術最適合從未或很少改變的圖層內容。注意必須為CGImageRef類型)
    • 將一個委托對象分配給圖層,讓代理繪制圖層的內容。(此技術最適合可能會周期性更改并可由外部對象(如視圖)提供的圖層內容。)
    • 定義一個layer的子類并覆蓋其繪圖方法,自己提供圖層內容。(如果您要創建自定義層子類,或者如果要更改圖層的基本繪圖行為,則此技術是適用的。)
  • contentsRect(CGRect):默認是{0, 0, 1,1}通俗來說就是將layer看成單位矩形,取其中的一部分。所有參數一般是[0-1]。

  • contentsGravity(NSString):決定分配給contents屬性圖像一什么方式呈現,一般分配給該屬性的值分為兩類:

    • 基于位置的重力常數允許將圖像定向到圖層邊界矩形的特定邊緣或角落,而 不縮放圖像。具體見 PS-1
    • 基于縮放的重力常數允許來拉伸圖像。具體見 PS-2
  • contentsScale(CGFloat):默認值是1.0,屬性主要作用是適應Retina屏與非Retina屏的,如果是Retina屏則設置為2.0。一般可以這樣設置[UIScreen mainScreen].scale

  • contentsCenter(CGFloat):默認是{0, 0, 1,1},在contentsRect的基礎上確定縮放比例,主要決定因子是前兩個元素,決定了原圖縱橫/現圖縱橫的比例,如:0.2則原圖被放大五倍!!!

  • opaque(BOOL):由-drawInContext提供的圖層內容是完全不透明的。 默認為NO。

  • opacity(float):layer的透明度,默認是1。

還有一些常見的屬性:如frame, hidden, backgroundColor, cornerRadius, borderWidth, borderColor, shadowColor,shadowOpacity, shadowOffset, shadowRadius, shadowPath 等。

除此之外,核心動畫對它所屬的CAAnimation和CALayer類擴展了NSKeyValueCoding的協議 --- 必須使用setValue:forKeyPath:valueForKeyPath:方法來設置和獲取這些字段,并增加了關鍵路徑對CGPointCGRectCGSize,和CATransform3D類型的支持(表-1)。所以可以直接使用KVC對其 屬性 進行賦取值,在這里需要特別注意對transform等結構體來使用keyPath的情形(表-2)

表-1

C類 包裝類
CGPoint /CGSize/CGRect/CATransform3D NSValue

表-2

字段路徑 包裝類及描述
transform.translation NSValue(包含CGSize數據類型) , 在x和y軸上平移的量
transform. translation.x NSNunber , 沿x軸平移
transform. translation.y NSNunber , 沿y軸平移
transform. translation.z NSNunber , 沿z軸平移
transform. scale NSNunber , xyz三個比例因子的平均值
transform. scale.x NSNunber , 沿x軸縮放
transform. scale.y NSNunber , 沿y軸縮放
transform. scale.z NSNunber , 沿z軸縮放
transform. rotation NSNunber , 沿z軸旋轉的弧度,與transform. rotation.z相同
transform. rotation.x NSNunber , 沿x軸旋轉的弧度
transform. rotation.y NSNunber , 沿y軸旋轉的弧度
transform. rotation.z NSNunber , 沿z軸旋轉的弧度
*** ***
position NSValue
position.x NSNunber
position.y NSNunber
*** ***
bounds/frame NSValue
bounds.origin 同position
bounds. size NSValue
bounds. size.width NSNumber
bounds. size.height NSNumber

PS-1:基于位置的重力常數:

CA_EXTERN NSString * const kCAGravityCenter
CA_EXTERN NSString * const kCAGravityTop
CA_EXTERN NSString * const kCAGravityBottom
CA_EXTERN NSString * const kCAGravityLeft
CA_EXTERN NSString * const kCAGravityRight
CA_EXTERN NSString * const kCAGravityTopLeft
CA_EXTERN NSString * const kCAGravityTopRight
CA_EXTERN NSString * const kCAGravityBottomLeft
CA_EXTERN NSString * const kCAGravityBottomRight
PS-1

PS-2:基于縮放的重力常數:

CA_EXTERN NSString * const kCAGravityResize
CA_EXTERN NSString * const kCAGravityResizeAspect
CA_EXTERN NSString * const kCAGravityResizeAspectFill
PS-2
2、方法:
  • + (nullable id)defaultValueForKey:(NSString *)key;:在使用KVC時為鍵提供默認值。一般進行復寫來覆蓋原有方法進行默認值設置。

  • - (void)display;:重新加載圖層的內容。 用法:如果實現了委托方法,默認會調用displayLayer:委托方法。否則,display方法會調用drawInContext方法,然后更新圖層的“contents”屬性。但是一般不主動調用!

  • - (void)setNeedsDisplay;- (void)setNeedsDisplayInRect:(CGRect)r;:與上面的方法差不多,但是可以主動調用。如果設置了rect,則只有該層的該區域無效。

  • - (void)displayIfNeeded;:繪圖系統在需要時自動調用,如果已經調用了setNeedsDisplay,該方法無效。

  • - (void)drawInContext:(CGContextRef)ctx;默認的display方法會創建一個視圖圖形上下文并將其傳遞給drawInContext:方法,與[UIView drawRect:]方法相似。


代理方法:

  • - (void)displayLayer:(CALayer *)layer;如果實現了代理同時定義了此方法則,-display方法會調用此方法,該實現負責創建位圖并將其分配給圖層的contents屬性。

  • - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)cox; -drawInContext調用此方法,創建一個圖形上下文來繪制該位圖,然后調用委托方法來填充位圖。如果-displayLayer: 方法存在,則該方法不調用,無效。

  • - (void)layerWillDraw:(CALayer *)layer:新加的方法,如果定義,則由-display方法的默認實現調用。 允許代理在-drawLayer之前配置影響內容的任何圖層狀態。同樣,如果-displayLayer: 方法存在,則該方法不調用,無效。

所以綜以上方法總結layer方法響應鏈有兩種:

  • [layer setNeedDisplay] / [layer displayIfNeed] -> [layer display] -> [layerDelegate displayLayer:] 。
  • [layer setNeedDisplay] / [layer displayIfNeed] -> [layer display] -> [layer drawInContext:] -> [layerDelegate drawLayer: inContext:]。

關于動畫的方法:

  • (void)addAnimation:(CAAnimation *)anim forKey:(nullable NSString *)key;

  • (void)removeAllAnimations;

  • (void)removeAnimationForKey:(NSString *)key;

示例代碼:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    _blueLayer = [CALayer layer];
    [_blueLayer setValue:(__bridge id)[UIColor blueColor].CGColor forKeyPath:@"backgroundColor"];
    [_blueLayer setValue:[NSValue valueWithCGPoint:CGPointZero] forKeyPath:@"anchorPoint"];
    [_blueLayer setValue:[NSValue valueWithCGRect:CGRectMake(0, 0, 1, 1)] forKeyPath:@"contentsRect"];
    [_blueLayer setValue:(__bridge id)[[UIImage imageNamed:@"boy"] CGImage] forKeyPath:@"contents"];
    [_blueLayer setValue:[NSValue valueWithCGRect:CGRectMake(50, 100, 200, 350)] forKeyPath:@"frame"];
    _blueLayer.delegate = self;
    [self.view.layer addSublayer:_blueLayer];
}
- (void)displayLayer:(CALayer *)layer
{
    if (once) {
        [_blueLayer setValue:[NSValue valueWithCGRect:CGRectMake(0, 0, 1, 0.7)] forKeyPath:@"contentsRect"];
        [_blueLayer setValue:(__bridge id)[[UIImage imageNamed:@"boy"] CGImage] forKeyPath:@"contents"];

    }else{
        [_blueLayer setValue:[NSValue valueWithCGRect:CGRectMake(0, 0, 1, 1)] forKeyPath:@"contentsRect"];
        [_blueLayer setValue:(__bridge id)[[UIImage imageNamed:@"girl"] CGImage] forKeyPath:@"contents"];
    }
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [_blueLayer setValue:kCAGravityResizeAspect forKeyPath:@"contentsGravity"];
    if (once) {
        once = NO;
        [UIView animateWithDuration:10.0 animations:^{
            [_blueLayer setValue:[NSNumber numberWithFloat:100] forKeyPath:@"transform.translation.x"];
            [_blueLayer setValue:[NSNumber numberWithFloat:100] forKeyPath:@"transform.translation.y"];
        }];
    }else{
        [UIView animateWithDuration:10.0 animations:^{
            [_blueLayer setValue:[NSValue valueWithCATransform3D:CATransform3DIdentity] forKeyPath:@"transform"];
        }];
        once = YES;
    }
    [_blueLayer setNeedsDisplay];
}
- (void)dealloc
{
    // 在這里代理一定要置空!否則控制器無法釋放
    _blueLayer.delegate = nil;
}
效果圖
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容