一、相關知識點
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 繼承于 UIResponder 和 NSObject
- CALayer 繼承與 NSObject
- UIView 響應事件
- CALayer 顯示內容
4.CALayer的存在意義?
- 由于Mac OS 和 iOS 在鍵鼠操作與多點觸控上有本質區別,所以在渲染層 *CALayer} 單獨封裝出來。
- 這也就是為什么 iOS 有 UIKit、UIView,Mac OS 有 AppKit、NSView 的原因
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
-
Layer
是View
的核心內容,View
是Layer
的代理 - 先給一個結論,
UIImageView
是通過UIView
的Layer.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];