iOS高級動畫之Layer與隱式動畫詳解

一、相關知識點

1.官方架構

  • Graphics Hardware可以理解為GPU
  • Metal、Core Graphics是對GPU的操作,由C語言封裝
  • Core Animation操作Metal、Core Graphics
  • UIKit iOS UI框架
  • AppKit相當于 Mac OS 的 UIKit
A30B46DB-8E85-4745-BA47-C392D65ABA6E.png

2.Core Animation

  • Core Animation 并不是單獨的框架,他位于 QuartzCore
  • Core Animation 包含了 CAAnimation
coraanimation.png

3.UIView和CALayer

  • UIView 繼承于 UIResponderNSObject
  • CALayer 繼承與 NSObject
  • UIView 響應事件
  • CALayer 顯示內容

4.CALayer的存在意義?

  • 由于Mac OSiOS 在鍵鼠操作與多點觸控上有本質區別,所以在渲染層 *CALayer} 單獨封裝出來。
  • 這也就是為什么 iOSUIKitUIViewMac OSAppKitNSView 的原因

5. CALayer的屬性AnchorPoint

  • AnchorPoint 錨點 默認值為 (0.5,0.5)
  • 錨點是單位坐標取值區間 [0,1]
5.1驗證AnchorPoint的變化是否與Position有關
  • 5.1.1試驗AnchorPoint {1,1}
    subViewOne = [[UIView alloc] initWithFrame:CGRectMake(100.f, 100.f, 100.f, 100.f)];
    subViewOne.backgroundColor = [UIColor lightGrayColor];
    [self.view addSubview:subViewOne];

    subView = [[EOCViewOne alloc] init];
    subView.frame = CGRectMake(100.f, 100.f, 100.f, 100.f);
    subView.layer.anchorPoint = CGPointMake(1, 1);
    subView.backgroundColor = [UIColor redColor];
    [self.view addSubview:subView];
    
    NSLog(@"position %@", NSStringFromCGPoint(subView.layer.position));

4B7EA3B7-0937-4347-BC75-F4AA2D86222A.png

NSLog : position {150,150}

  • 5.1.2試驗AnchorPoint {0,0}
    subViewOne = [[UIView alloc] initWithFrame:CGRectMake(100.f, 100.f, 100.f, 100.f)];
    subViewOne.backgroundColor = [UIColor lightGrayColor];
    [self.view addSubview:subViewOne];

    subView = [[EOCViewOne alloc] init];
    subView.frame = CGRectMake(100.f, 100.f, 100.f, 100.f);
    subView.layer.anchorPoint = CGPointMake(1, 1);
    subView.backgroundColor = [UIColor redColor];
    [self.view addSubview:subView];
    
    NSLog(@"position %@", NSStringFromCGPoint(subView.layer.position));

anchorpoint2.png

NSLog : position {150,150}

  • 5.1.3 結論
- AnchorPoint 的改變與 position 無關,但是frame改變了
- AnchorPoint的 位置 就是 position的位置
- position 是 AnchorPoint 在父view上的位置
- frema 本質是根據 position、bouns、AnchorPoint計算出來的
- 我要釘一副畫(bouns)到到墻上,那么我這個釘子釘的位置就是 position 
- 先改變AnchorPoint在圖形上的位置,再讓圖形隨著AnchorPoint,把AnchorPoint放到position的位置
  • 5.1.4 拓展思考
    如果我們先修改 AnchorPoint 再設置 frame,會怎么樣呢?
  subView.layer.anchorPoint = CGPointMake(1, 1);
  subView.frame = CGRectMake(100.f, 100.f, 100.f, 100.f);
    
//兩個view重合,anchorPoint改變,position因frame而改變
//position {200,200} ; anchorPoint {1,1}
position.png
  • 5.1.5 AnchorPoint、frame、position的推導公式
frame.origin.x = position.x - anchorPoint.x * bouns.size.width
frame.origin.y = position.x - anchorPoint.y * bouns.size.height

6.transform后的frame神奇變化

  • 我們旋轉45度
subView.transform = CGAffineTransformMakeRotation(M_PI_2/2);
transform.png
- frame 改變
- frame 為白色區域
- frame 不等于bouns

7. AnchorPoint與動畫的結合使用

  • 通過改變錨點位置,結合NSTimer,制作時鐘動畫
clock.png

8.Layer.content

8.1 直接設置content
  • LayerView的核心內容,ViewLayer 的代理
  • 先給一個結論,UIImageView 是通過 UIViewLayer.content實現,在這里 content 是一個位圖指針:
//_uiview 為 (UIView *)類型
_uiview = [[UIView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
    _uiview.layer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"1.png"].CGImage);
    [self.view addSubview:_uiview];
(lldb) po _uiview
<UIView: 0x7fdd7c637e80; frame = (100 100; 100 100); layer = <CALayer: 0x6000002327c0>>

(lldb) po _uiview.layer.contents
<CGImage 0x6000001c2df0>
    <<CGColorSpace 0x60000002a220> (kCGColorSpaceICCBased; kCGColorSpaceModelMonochrome; Generic Gray Gamma 2.2 Profile)>
        width = 81, height = 65, bpc = 8, bpp = 16, row bytes = 162 
        kCGImageAlphaLast | 0 (default byte order) 
        is mask? No, has mask? No, has matte? No, should interpolate? Yes
8.2 添加一個subLayer.content
  • 必須要調用setNeedsDisplay才會觸發代理方法- (void)displayLayer:(CALayer *)layer- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
  • 如果- (void)displayLayer:(CALayer *)layer 和同時存在只會觸發- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
  • UIView的drawRect本質就是通過layer的代理方法- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx來調用的
  • 實例代碼:
    imgLayer = [CALayer layer];
    imgLayer.frame = CGRectMake(100.f, 100.f, 100.f, 100.f);
   // imgLayer.contents = (__bridge id)[UIImage imageNamed:@"bubble.png"].CGImage;
    imgLayer.delegate = self;
    [self.view.layer addSublayer:imgLayer];
    
    [imgLayer setNeedsDisplay];//必須要調用這個才會出發代理
- (void)displayLayer:(CALayer *)layer {
    //寫了此代理方法就不會執行下面的代理方法
    imgLayer.contents = (__bridge id)[UIImage imageNamed:@"1.png"].CGImage;
    NSLog(@"displayLayer");
    
}

- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
    //UIView的drawRect本質就通過此代理方法調用
    NSLog(@"drawLayer");
    
}
  • 堆棧調用詳情:
stack2.png

9.layer的delegate不可隨意設置

9.1 一種難以追蹤的crush情況
  • 在UIView里自定義的layer變量的delegate不能設置為UIView自身
  • 原理是因為(UIView *) self.layer.delegate = self; 如果其他layer.delegate = self 會產生素亂,UIView無法判斷讓哪個layer遵循代理的方法
- (instancetype)initWithFrame:(CGRect)frame {
    
    if (self = [super initWithFrame:frame]) {
       
        _delegate = [[EOCLayerDelegate alloc] init];
        CALayer *layer = [CALayer layer];
        layer.delegate = self; //設置此行代碼crush
        layer.backgroundColor = [UIColor redColor].CGColor;
        [self.layer addSublayer:layer];
        
    }
    
    return self;
}
9.2 解決辦法
  • 重寫一個遵循<CAAnimationDelegate>的類,讓子layer的delegate設置為這個類
@interface EOCLayerDelegate : NSObject<CAAnimationDelegate>

- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;

@end

@implementation EOCLayerDelegate

- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
    
    NSLog(@"drawLayer2222");
    
}

@end
@interface EOCView () {
    EOCLayerDelegate *_delegate ;
}
@end

@implementation EOCView
- (instancetype)initWithFrame:(CGRect)frame {
    
    if (self = [super initWithFrame:frame]) {
       
        _delegate = [[EOCLayerDelegate alloc] init];
        CALayer *layer = [CALayer layer];
        layer.delegate = (id)_delegate;
        layer.backgroundColor = [UIColor redColor].CGColor;
        [self.layer addSublayer:layer];
        
    }
    
    return self;
}
9.3 由 9.2 解決方案引起的并發crush
  • 經過排除發現添加的子layer在 Controller 的 dealloc 不會釋放,所以我們必要手動 remove 處理
- (void)dealloc {
    
    [imgLayer removeFromSuperlayer];
    
}

10. layer遮罩層

  • 先上一張底圖,我們在上面做 兩種 mask特效
    imageView = [[UIImageView alloc] initWithFrame:CGRectMake(100.f, 100.f, 200.f, 200.f)];
    imageView.image = [UIImage imageNamed:@"bg-mine.png"];
    [self.view addSubview:imageView];
  • 背景圖:
bg.png
  • mask圖:
mask.png
  • 為背景圖添加遮罩效果
    UIImage *imageTwo = [UIImage imageNamed:@"bubble.png"];
    CALayer *imageLayer = [CALayer layer];
    imageLayer.frame = imageView.bounds;
    imageLayer.contents = (__bridge id)imageTwo.CGImage;   
    imageView.layer.mask = imageLayer;
  • 添加mask后的效果:
mask1.png
  • 設置mask的顯示區域
imageLayer.contentsCenter = CGRectMake(0.5f, 0.5, 0.1, 0.1);
mask2.png

Layer Animation

1. 隱式動畫效果展示

  • layer層有隱式動畫(默認自動調用的動畫效果)
  • 我們通過切換layer層的背景顏色來驗證
  • 結論:每次切換顏色都有一個漸變的動畫效果,在UIView是不會觸發這種效果的
  • 同理,我們改變layer.bouns也會帶有隱式動畫的效果
     //layer層有隱式動畫
    eocLayer = [EOCLayer layer];
    eocLayer.backgroundColor = [UIColor lightGrayColor].CGColor;
    eocLayer.frame = CGRectMake(100.f, 100.f, 100.f, 100.f);
    [self.view.layer addSublayer:eocLayer];
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        eocLayer.backgroundColor = [UIColor colorWithRed:arc4random_uniform(256.f)/255.f green:arc4random_uniform(256.f)/255.f blue:arc4random_uniform(256.f)/255.f alpha:1.f].CGColor;
}

2. 隱式動畫的底層實現

  • 核心方法+ (nullable id<CAAction>)defaultActionForKey:(NSString *)event;
code.png
1. 如果我們設置了layer的代理,那么會去方法 -actionForLayer:forKey; 尋找key值,根據key來確定要執行的action(動畫)
 - 1.1 如果 -(id<CAAction>)actionForLayer:forKey;  返回的是非nil包括 [NSNull null]), 停止 ; nil繼續下一個步驟
 - 1.2 由于 actionForLayer 是CAAction的代理方法,選擇寫在Controller下,也可在UIView.m下用于開啟隱式動畫

2. 從layer的 `action` 字典里面搜索key
3. 從layer的 `style` 字典里面搜索key
4. 根據方法 +defaultActionForKey: 看他的返回值
5.如果以上步驟都沒有結果,那么調用方法 -actionForKey,返回一個默認的動畫,如<CABasicAnimation>
6. -addAnimation 就會把這個動畫添加到layer上

 * If any of these steps results in a non-nil action object, the
 * following steps are ignored.
 *如果上述的步驟返回一個非nil的結果,那么當前步驟就被
 *停止了 (不包含步驟5.6. 步驟根據nil或者非nil決定最后的動畫)

  • 所以通過了解了隱式動畫的底層執行步驟,我們可以實現貍貓換太子的方式替換隱式動畫
  • 我可以選擇在 -actionForKey 或者 -addAnimation 中重寫一個隱式動畫
- (void)addAnimation:(CAAnimation *)anim forKey:(NSString *)key {
    
    CABasicAnimation *animation = [CABasicAnimation animation];
    animation.duration = 1.f;
    animation.fromValue = (__bridge id)[UIColor blackColor].CGColor;
    animation.toValue = (__bridge id)[UIColor redColor].CGColor;
    
    [super addAnimation:animation forKey:key]; //最后執行的動畫以此處為準
    
}

- (id<CAAction>)actionForKey:(NSString *)event {
    
    NSLog(@"actionForKey %@ action %@", event, [super actionForKey:event]);
    
     CABasicAnimation *animation = [CABasicAnimation animation];
    animation.duration = 3.f;
    animation.fromValue = (__bridge id)[UIColor whiteColor].CGColor;
    animation.toValue = (__bridge id)[UIColor greenColor].CGColor;
    
    return animation; 
    //將此animation傳遞給 -addAnimation
    //如果傳遞nil,在 -addAnimation中也沒有自定義動畫,那么layer就不會執行隱式動畫
    
}

3. 為什么UIView沒有隱式動畫?

  • 通過上面的探索,我在UIView.m下,在-actionForLayer:forKey中返回nil,那么改變 UIView 的backgroundColor,UIView 也實現了隱式動畫
  • 結論:蘋果在 UIView 中,讓 -actionForLayer:forKey 的返回值為 [NSNull null] 導致了 UIView 失去了隱式動畫的效果
  • 同理: [UIView animation] 也是對 layer 隱式動畫代理的操作

事務

  • 事務可以影響動畫,比如動畫的時間
  • 通常在默認的動畫模式下才生效,如果在layer的代理方法里設置過動畫,則以代理的動畫設置為準
  • 代理可以用于取消action,從而達到關閉動畫的效果
 //事務
    [CATransaction begin];
    [CATransaction setAnimationDuration:5.f]; //重置動畫時間
//    [CATransaction setDisableActions:YES]; //action無效化,即關閉動畫

         //animation code area

    [CATransaction commit];
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 在iOS中隨處都可以看到絢麗的動畫效果,實現這些動畫的過程并不復雜,今天將帶大家一窺ios動畫全貌。在這里你可以看...
    每天刷兩次牙閱讀 8,551評論 6 30
  • 在iOS中隨處都可以看到絢麗的動畫效果,實現這些動畫的過程并不復雜,今天將帶大家一窺iOS動畫全貌。在這里你可以看...
    F麥子閱讀 5,141評論 5 13
  • 在iOS實際開發中常用的動畫無非是以下四種:UIView動畫,核心動畫,幀動畫,自定義轉場動畫。 1.UIView...
    請叫我周小帥閱讀 3,142評論 1 23
  • Core Animation Core Animation,中文翻譯為核心動畫,它是一組非常強大的動畫處理API,...
    45b645c5912e閱讀 3,048評論 0 21
  • 前言 本文只要描述了iOS中的Core Animation(核心動畫:隱式動畫、顯示動畫)、貝塞爾曲線、UIVie...
    GitHubPorter閱讀 3,649評論 7 11